Adding Afrikaans locale and i18n expansion

NousResearch/hermes-agent · cluster 21961

Data as of

Nothing open on GitHub. Fix merged in #22914. Check it covers your case.

The short answer

0open issues
0open PRs
5items grouped
Known?
Yes. 5 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #22914. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
Nothing open on GitHub.

These items focus on adding Afrikaans language support and expanding internationalization coverage, including localizing gateway commands, a web dashboard, and static messages across multiple locales.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #22914 feat(i18n): localize all gateway commands + web dashboard, add 8 new locales (16 total)

    merged to main

    files: agent/i18n.py, gateway/run.py, locales/af.yaml, locales/de.yaml, locales/en.yaml, locales/es.yaml, locales/fr.yaml, locales/ga.yaml, locales/hu.yaml, locales/it.yaml, locales/ja.yaml, locales/ko.yaml, locales/pt.yaml, locales/ru.yaml, locales/tr.yaml, locales/uk.yaml, locales/zh-hant.yaml, locales/zh.yaml, plugins/hermes-achievements/dashboard/dist/index.js, plugins/kanban/dashboard/dist/index.js (+21 more)

    Show the diff
    • agent/i18n.py
      @@ -39,20 +39,45 @@
       
       logger = logging.getLogger(__name__)
       
      -SUPPORTED_LANGUAGES: tuple[str, ...] = ("en", "zh", "ja", "de", "es", "fr", "tr", "uk")
      +SUPPORTED_LANGUAGES: tuple[str, ...] = (
      +    "en", "zh", "zh-hant", "ja", "de", "es", "fr", "tr", "uk",
      +    "af", "ko", "it", "ga", "pt", "ru", "hu",
      +)
       DEFAULT_LANGUAGE = "en"
       
       # Accept a few natural aliases so users who type "chinese" / "zh-CN" / "jp"
       # get the right catalog instead of silently falling back to English.
       _LANGUAGE_ALIASES: dict[str, str] = {
           "english": "en", "en-us": "en", "en-gb": "en",
      -    "chinese": "zh", "mandarin": "zh", "zh-cn": "zh", "zh-tw": "zh", "zh-hans": "zh", "zh-hant": "zh",
      +    # Simplified Chinese — explicit codes route here; bare "chinese" / "mandarin"
      +    # also default to Simplified since that's the larger user base.
      +    "chinese": "zh", "mandarin": "zh", "zh-cn": "zh", "zh-hans": "zh", "zh-sg": "zh",
      +    # Traditional Chinese — distinct catalog.  Cover Taiwan / Hong Kong / Macau
      +    # locale tags plus the common "traditional" alias.
      +    "traditional-chinese": "zh-hant", "traditional_chinese": "zh-hant",
      +    "zh-tw": "zh-hant", "zh-hk": "zh-hant", "zh-mo": "zh-hant",
           "japanese": "ja", "jp": "ja", "ja-jp": "ja",
      -    "german": "de", "deutsch": "de", "de-de": "de",
      -    "spanish": "es", "español": "es", "espanol": "es", "es-es": "es", "es-mx": "es",
      +    "german": "de", "deutsch": "de", "de-de": "de", "de-at": "de", "de-ch": "de",
      +    "spanish": "es", "español": "es", "espanol": "es", "es-es": "es", "es-mx": "es", "es-ar": "es",
           "french": "fr", "français": "fr", "france": "fr", "fr-fr": "fr", "fr-be": "fr", "fr-ca": "fr", "fr-ch": "fr",
           "ukrainian": "uk", "ukrainisch": "uk", "українська": "uk", "uk-ua": "uk", "ua": "uk",
           "turkish": "tr", "türkçe": "tr", "tr-tr": "tr",
      +    # Afrikaans — South African Dutch-derived language; "af-ZA" is the common BCP-47 tag.
      +    "afrikaans": "af", "af-za": "af",
      +    # Korean
      +    "korean": "ko", "한국어": "ko", "ko-kr": "ko",
      +    # Italian
      +    "italian": "it", "italiano": "it", "it-it": "it", "it-ch": "it",
      +    # Irish (Gaeilge) — ga is the BCP-47 code
      +    "irish": "ga", "gaeilge": "ga", "ga-ie": "ga",
      +    # Portuguese — bare "portuguese" routes to European Portuguese; pt-br
      +    # is in the same family but rendered identically here (no separate br catalog).
      +    "portuguese": "pt", "português": "pt", "portugues": "pt",
      +    "pt-pt": "pt", "pt-br": "pt", "brazilian": "pt", "brasileiro": "pt",
      +    # Russian
      +    "russian": "ru", "русский": "ru", "ru-ru": "ru",
      +    # Hungarian
      +    "hungarian": "hu", "magyar": "hu", "hu-hu": "hu",
       }
       
       _catalog_cache: dict[str, dict[str, str]] = {}
    • gateway/run.py
      @@ -5532,7 +5532,7 @@ async def _handle_message(self, event: MessageEvent) -> Optional[str]:
                           invalidation_reason="stop_command",
                       )
                       logger.info("STOP for session %s — agent interrupted, session lock released", _quick_key)
      -                return EphemeralReply("⚡ Stopped. You can continue this session.")
      +                return EphemeralReply(t("gateway.stop.stopped"))
       
                   # /reset and /new must bypass the running-agent guard so they
                   # actually dispatch as commands instead of being queued as user
      @@ -7684,11 +7684,11 @@ async def _handle_reset_command(self, event: MessageEvent) -> Union[str, Ephemer
                   session_info = ""
       
               if new_entry:
      -            header = self._telegram_topic_new_header(source) or "✨ Session reset! Starting fresh."
      +            header = self._telegram_topic_new_header(source) or t("gateway.reset.header_default")
               else:
                   # No existing session, just create one
                   new_entry = self.session_store.get_or_create_session(source, force_new=True)
      -            header = self._telegram_topic_new_header(source) or "✨ New session started!"
      +            header = self._telegram_topic_new_header(source) or t("gateway.reset.header_new")
       
               # Set session title if provided with /new <title>
               _title_arg = event.get_command_args().strip()
      @@ -7699,18 +7699,18 @@ async def _handle_reset_command(self, event: MessageEvent) -> Union[str, Ephemer
                       sanitized = SessionDB.sanitize_title(_title_arg)
                   except ValueError as e:
                       sanitized = None
      -                _title_note = f"\n⚠️ Title rejected: {e}"
      +                _title_note = t("gateway.reset.title_rejected", error=str(e))
                   if sanitized:
                       try:
                           self._session_db.set_session_title(new_entry.session_id, sanitized)
      -                    header = f"✨ New session started: {sanitized}"
      +                    header = t("gateway.reset.header_titled", title=sanitized)
                       except ValueError as e:
      -                    _title_note = f"\n⚠️ {e} — session started untitled."
      +                    _title_note = t("gateway.reset.title_error_untitled", error=str(e))
                       except Exception:
                           pass
                   elif not _title_note:
                       # sanitize_title returned empty (whitespace-only / unprintable)
      -                _title_note = "\n⚠️ Title is empty after cleanup — session started untitled."
      +                _title_note = t("gateway.reset.title_empty_untitled")
               header = header + _title_note
       
               # When /new runs inside a Telegram DM topic lane, rewrite the
      @@ -7736,7 +7736,7 @@ async def _handle_reset_command(self, event: MessageEvent) -> Union[str, Ephemer
               # Append a random tip to the reset message
               try:
                   from hermes_cli.tips import get_random_tip
      -            _tip_line = f"\n✦ Tip: {get_random_tip()}"
      +            _tip_line = t("gateway.reset.tip", tip=get_random_tip())
               except Exception:
                   _tip_line = ""
       
      @@ -7753,8 +7753,8 @@ async def _handle_profile_command(self, event: MessageEvent) -> str:
               profile_name = get_active_profile_name()
       
               lines = [
      -            f"👤 **Profile:** `{profile_name}`",
      -            f"📂 **Home:** `{display}`",
      +            t("gateway.profile.header", profile=profile_name),
      +            t("gateway.profile.home", home=display),
               ]
       
               return "\n".join(lines)
      @@ -7790,7 +7790,7 @@ async def _handle_kanban_command(self, event: MessageEvent) -> str:
               try:
                   output = await asyncio.to_thread(run_slash, text)
               except Exception as exc:  # pragma: no cover - defensive
      -            return f"⚠ kanban error: {exc}"
      +            return t("gateway.kanban.error_prefix", error=exc)
       
               # Auto-subscribe on create. Parse the task id from the CLI's standard
               # success line ("Created t_abcd  (ready, assignee=...)"). If the user
      @@ -7825,8 +7825,8 @@ def _sub():
                               await asyncio.to_thread(_sub)
                               output = (
                                   output.rstrip()
      -                            + f"\n(subscribed — you'll be notified when {task_id} "
      -                              f"completes or blocks)"
      +                            + "\n"
      +                            + t("gateway.kanban.subscribed_suffix", task_id=task_id)
                               )
                       except Exception as exc:
                           logger.warning("kanban create auto-subscribe failed: %s", exc)
      @@ -7834,8 +7834,8 @@ def _sub():
               # Gateway messages have practical length caps; truncate long
               # listings to keep the UX reasonable.
               if len(output) > 3800:
      -            output = output[:3800] + "\n… (truncated; use `hermes kanban …` in your terminal for full output)"
      -        return output or "(no output)"
      +            output = output[:3800] + "\n" + t("gateway.kanban.truncated_suffix")
      +        return output or t("gateway.kanban.no_output")
       
           async def _handle_status_command(self, event: MessageEvent) -> str:
               """Handle /status command."""
      @@ -7879,23 +7879,23 @@ async def _handle_status_command(self, event: MessageEvent) -> str:
                       db_total_tokens = 0
       
               lines = [
      -            "📊 **Hermes Gateway Status**",
      +            t("gateway.status.header"),
                   "",
      -            f"**Session ID:** `{session_entry.session_id}`",
      +            t("gateway.status.session_id", session_id=session_entry.session_id),
               ]
               if title:
      -            lines.append(f"**Title:** {title}")
      +            lines.append(t("gateway.status.title", title=title))
               lines.extend([
      -            f"**Created:** {session_entry.created_at.strftime('%Y-%m-%d %H:%M')}",
      -            f"**Last Activity:** {session_entry.updated_at.strftime('%Y-%m-%d %H:%M')}",
      -            f"**Tokens:** {db_total_tokens:,}",
      -            f"**Agent Running:** {'Yes ⚡' if is_running else 'No'}",
      +            t("gateway.status.created", timestamp=session_entry.created_at.strftime('%Y-%m-%d %H:%M')),
      +            t("gateway.status.last_activity", timestamp=session_entry.updated_at.strftime('%Y-%m-%d %H:%M')),
      +            t("gateway.status.tokens", tokens=f"{db_total_tokens:,}"),
      +            t("gateway.status.agent_running", state=t("gateway.status.state_yes") if is_running else t("gateway.status.state_no")),
               ])
               if queue_depth:
      -            lines.append(f"**Queued follow-ups:** {queue_depth}")
      +            lines.append(t("gateway.status.queued", count=queue_depth))
               lines.extend([
                   "",
      -            f"**Connected Platforms:** {', '.join(connected_platforms)}",
      +            t("gateway.status.platforms", platforms=', '.join(connected_platforms)),
               ])
       
               return "\n".join(lines)
      @@ -7919,7 +7919,7 @@ async def _handle_agents_command(self, event: MessageEvent) -> str:
                       {
                           "session_key": session_key,
                           "elapsed": elapsed,
      -                    "state": "starting" if is_pending else "running",
      +                    "state": t("gateway.agents.state_starting") if is_pending else t("gateway.agents.state_running"),
                           "session_id": "" if is_pending else str(getattr(agent, "session_id", "") or ""),
                           "model": "" if is_pending else str(getattr(agent, "model", "") or ""),
                       }
      @@ -7942,14 +7942,14 @@ async def _handle_agents_command(self, event: MessageEvent) -> str:
               ]
       
               lines = [
      -            "🤖 **Active Agents & Tasks**",
      +            t("gateway.agents.header"),
                   "",
      -            f"**Active agents:** {len(agent_rows)}",
      +            t("gateway.agents.active_agents", count=len(agent_rows)),
               ]
       
               if agent_rows:
                   for idx, row in enumerate(agent_rows[:12], 1):
      -                current = " · this chat" if row["session_key"] == current_session_key else ""
      +                current = t("gateway.agents.this_chat") if row["session_key"] == current_session_key else ""
                       sid = f" · `{row['session_id']}`" if row["session_id"] else ""
                       model = f" · `{row['model']}`" if row["model"] else ""
                       lines.append(
      @@ -7957,12 +7957,12 @@ async def _handle_agents_command(self, event: MessageEvent) -> str:
                           f"{format_uptime_short(row['elapsed'])}{sid}{model}{current}"
                       )
                   if len(agent_rows) > 12:
      -                lines.append(f"... and {len(agent_rows) - 12} more")
      +                lines.append(t("gateway.agents.more", count=len(agent_rows) - 12))
       
               lines.extend(
                   [
                       "",
      -                f"**Running background processes:** {len(running_processes)}",
      +                t("gateway.agents.running_processes", count=len(running_processes)),
                   ]
               )
               if running_processes:
      @@ -7975,18 +7975,18 @@ async def _handle_agents_command(self, event: MessageEvent) -> str:
                           f"{format_uptime_short(int(proc.get('uptime_seconds', 0)))} · `{cmd}`"
                       )
                   if len(running_processes) > 12:
      -                lines.append(f"... and {len(running_processes) - 12} more")
      +                lines.append(t("gateway.agents.more", count=len(running_processes) - 12))
       
               lines.extend(
                   [
                       "",
      -                f"**Gateway async jobs:** {len(background_tasks)}",
      +                t("gateway.agents.async_jobs", count=len(background_tasks)),
                   ]
               )
       
               if not agent_rows and not running_processes and not background_tasks:
                   lines.append("")
      -            lines.append("No active agents or running tasks.")
      +            lines.append(t("gateway.agents.none"))
       
               return "\n".join(lines)
       
      @@ -8015,7 +8015,7 @@ async def _handle_stop_command(self, event: MessageEvent) -> Union[str, Ephemera
                       invalidation_reason="stop_command_pending",
                   )
                   logger.info("STOP (pending) for session %s — sentinel cleared", session_key)
      -            return EphemeralReply("⚡ Stopped. The agent hadn't started yet — you can continue this session.")
      +            return EphemeralReply(t("gateway.stop.stopped_pending"))
               if agent:
                   # Force-clean the session lock so a truly hung agent doesn't
                   # keep it locked forever.
      @@ -8025,9 +8025,9 @@ async def _handle_stop_command(self, event: MessageEvent) -> Union[str, Ephemera
                       interrupt_reason=_INTERRUPT_REASON_STOP,
                       invalidation_reason="stop_command_handler",
                   )
      -            return EphemeralReply("⚡ Stopped. You can continue this session.")
      +            return EphemeralReply(t("gateway.stop.stopped"))
               else:
      -            return "No active task to stop."
      +            return t("gateway.stop.no_active")
       
           async def _handle_restart_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
               """Handle /restart command - drain active work, then restart the gateway."""
      @@ -8055,7 +8055,7 @@ async def _handle_restart_command(self, event: MessageEvent) -> Union[str, Ephem
                   count = self._running_agent_count()
                   if count:
                       return t("gateway.draining", count=count)
      -            return EphemeralReply("⏳ Gateway restart already in progress...")
      +            return EphemeralReply(t("gateway.restart.in_progress"))
       
               # Save the requester's routing info so the new gateway process can
               # notify them once it comes back online.
      @@ -8107,7 +8107,7 @@ async def _handle_restart_command(self, event: MessageEvent) -> Union[str, Ephem
                   self.request_restart(detached=True, via_service=False)
               if active_agents:
                   return t("gateway.draining", count=active_agents)
      -        return EphemeralReply("♻ Restarting gateway. If you aren't notified within 60 seconds, restart from the console with `hermes gateway restart`.")
      +        return EphemeralReply(t("gateway.restart.restarting"))
       
           def _is_stale_restart_redelivery(self, event: MessageEvent) -> bool:
               """Return True if this /restart is a Telegram re-delivery we already handled.
      @@ -8163,20 +8163,20 @@ async def _handle_help_command(self, event: MessageEvent) -> str:
               """Handle /help command - list available commands."""
               from hermes_cli.commands import gateway_help_lines
               lines = [
      -            "📖 **Hermes Commands**\n",
      +            t("gateway.help.header"),
                   *gateway_help_lines(),
               ]
               try:
                   from agent.skill_commands import get_skill_commands
                   skill_cmds = get_skill_commands()
                   if skill_cmds:
      -                lines.append(f"\n⚡ **Skill Commands** ({len(skill_cmds)} active):")
      +                lines.append(t("gateway.help.skill_header", count=len(skill_cmds)))
                       # Show first 10, then point to /commands for the rest
                       sorted_cmds = sorted(skill_cmds)
                       for cmd in sorted_cmds[:10]:
                           lines.append(f"`{cmd}` — {skill_cmds[cmd]['description']}")
                       if len(sorted_cmds) > 10:
      -                    lines.append(f"\n... and {len(sorted_cmds) - 10} more. Use `/commands` for the full paginated list.")
      +                    lines.append(t("gateway.help.more_use_commands", count=len(sorted_cmds) - 10))
               except Exception:
                   pass
               return _telegramize_command_mentions(
      @@ -8193,7 +8193,7 @@ async def _handle_commands_command(self, event: MessageEvent) -> str:
                   try:
                       requested_page = int(raw_args)
                   except ValueError:
      -                return "Usage: `/commands [page]`"
      +                return t("gateway.commands.usage")
               else:
                   requested_page = 1
       
      @@ -8204,15 +8204,15 @@ async def _handle_commands_command(self, event: MessageEvent) -> str:
                   skill_cmds = get_skill_commands()
                   if skill_cmds:
                       entries.append("")
      -                entries.append("⚡ **Skill Commands**:")
      +                entries.append(t("gateway.commands.skill_header"))
                       for cmd in sorted(skill_cmds):
      -                    desc = skill_cmds[cmd].get("description", "").strip() or "Skill command"
      +                    desc = skill_cmds[cmd].get("description", "").strip() or t("gateway.commands.default_desc")
                           entries.append(f"`{cmd}` — {desc}")
               except Exception:
                   pass
       
               if not entries:
      -            return "No commands available."
      +            return t("gateway.commands.none")
       
               from gateway.config import Platform
               page_size = 15 if event.source.platform == Platform.TELEGRAM else 20
      @@ -8222,19 +8222,19 @@ async def _handle_commands_command(self, event: MessageEvent) -> str:
               page_entries = entries[start:start + page_size]
       
               lines = [
      -            f"📚 **Commands** ({len(entries)} total, page {page}/{total_pages})",
      +            t("gateway.commands.header", total=len(entries), page=page, total_pages=total_pages),
                   "",
                   *page_entries,
               ]
               if total_pages > 1:
                   nav_parts = []
                   if page > 1:
      -                nav_parts.append(f"`/commands {page - 1}` ← prev")
      +                nav_parts.append(t("gateway.commands.nav_prev", page=page - 1))
                   if page < total_pages:
      -                nav_parts.append(f"next → `/commands {page + 1}`")
      +                nav_parts.append(t("gateway.commands.nav_next", page=page + 1))
                   lines.extend(["", " | ".join(nav_parts)])
               if page != requested_page:
      -            lines.append(f"_(Requested page {requested_page} was out of range, showing page {page}.)_")
      +            lines.append(t("gateway.commands.out_of_range", requested=requested_page, page=page))
               return _telegramize_command_mentions(
                   "\n".join(lines),
                   getattr(getattr(event, "source", None), "platform", None),
      @@ -8346,7 +8346,7 @@ async def _on_model_selected(
                                   custom_providers=custom_provs,
                               )
                               if not result.success:
      -                            return f"Error: {result.error_message}"
      +                            return t("gateway.model.error_prefix", error=result.error_message)
       
                               # Update cached agent in-place
                               cached_entry = None
      @@ -8390,8 +8390,8 @@ async def _on_model_selected(
       
                               # Build confirmation text
                               plabel = result.provider_label or result.target_provider
      -                        lines = [f"Model switched to `{result.new_model}`"]
      -                        lines.append(f"Provider: {plabel}")
      +                        lines = [t("gateway.model.switched", model=result.new_model)]
      +                        lines.append(t("gateway.model.provider_label", provider=plabel))
                               mi = result.model_info
                               from hermes_cli.model_switch import resolve_display_context_length
                               _sw_config_ctx = None
      @@ -8414,14 +8414,14 @@ async def _on_model_selected(
                                   config_context_length=_sw_config_ctx,
                               )
                               if ctx:
      -                            lines.append(f"Context: {ctx:,} tokens")
      +                            lines.append(t("gateway.model.context_label", tokens=f"{ctx:,}"))
                               if mi:
                                   if mi.max_output:
      -                                lines.append(f"Max output: {mi.max_output:,} tokens")
      +                                lines.append(t("gateway.model.max_output_label", tokens=f"{mi.max_output:,}"))
                                   if mi.has_cost_data():
      -                                lines.append(f"Cost: {mi.format_cost()}")
      -                            lines.append(f"Capabilities: {mi.format_capabilities()}")
      -                        lines.append("_(session only — use `/model <name> --global` to persist)_")
      +                                lines.append(t("gateway.model.cost_label", cost=mi.format_cost()))
      +                            lines.append(t("gateway.model.capabilities_label", capabilities=mi.format_capabilities()))
      +                        lines.append(t("gateway.model.session_only_hint"))
                               return "\n".join(lines)
       
                           metadata = self._thread_metadata_for_source(source, self._reply_anchor_for_event(event))
      @@ -8439,7 +8439,7 @@ async def _on_model_selected(
       
                   # Fallback: text list (for platforms without picker or if picker failed)
                   provider_label = get_label(current_provider)
      -            lines = [f"Current: `{current_model or 'unknown'}` on {provider_label}", ""]
      +            lines = [t("gateway.model.current_label", model=current_model or "unknown", provider=provider_label), ""]
       
                   try:
                       providers = list_authenticated_providers(
      @@ -8451,11 +8451,11 @@ async def _on_model_selected(
                           max_models=5,
                       )
                       for p in providers:
      -                    tag = " (current)" if p["is_current"] else ""
      +                    tag = t("gateway.model.current_tag") if p["is_current"] else ""
                           lines.append(f"**{p['name']}** `--provider {p['slug']}`{tag}:")
                           if p["models"]:
                               model_strs = ", ".join(f"`{m}`" for m in p["models"])
      -                        extra = f" (+{p['total_models'] - len(p['models'])} more)" if p["total_models"] > len(p["models"]) else ""
      +                        extra = t("gateway.model.more_models_suffix", count=p["total_models"] - len(p["models"])) if p["total_models"] > len(p["models"]) else ""
                               lines.append(f"  {model_strs}{extra}")
                           elif p.get("api_url"):
                               lines.append(f"  `{p['api_url']}`")
      @@ -8463,9 +8463,9 @@ async def _on_model_selected(
                   except Exception:
                       pass
       
      -            lines.append("`/model <name>` — switch model")
      -            lines.append("`/model <name> --provider <slug>` — switch provider")
      -            lines.append("`/model <name> --global` — persist")
      +            lines.append(t("gateway.model.usage_switch_model"))
      +            lines.append(t("gateway.model.usage_switch_provider"))
      +            lines.append(t("gateway.model.usage_persist"))
                   return "\n".join(lines)
       
               # Perform the switch
      @@ -8482,7 +8482,7 @@ async def _on_model_selected(
               )
       
               if not result.success:
      -            return f"Error: {result.error_message}"
      +            return t("gateway.model.error_prefix", error=result.error_message)
       
               # If there's a cached agent, update it in-place
               cached_entry = None
      @@ -8547,8 +8547,8 @@ async def _on_model_selected(
       
               # Build confirmation message with full metadata
               provider_label = result.provider_label or result.target_provider
      -        lines = [f"Model switched to `{result.new_model}`"]
      -        lines.append(f"Provider: {provider_label}")
      +        lines = [t("gateway.model.switched", model=result.new_model)]
      +        lines.append(t("gateway.model.provider_label", provider=provider_label))
       
               # Context: always resolve via the provider-aware chain so Codex OAuth,
               # Copilot, and Nous-enforced caps win over the raw models.dev entry.
      @@ -8574,13 +8574,13 @@ async def _on_model_selected(
                   config_context_length=_sw2_config_ctx,
               )
               if ctx:
      -            lines.append(f"Context: {ctx:,} tokens")
      +            lines.append(t("gateway.model.context_label", tokens=f"{ctx:,}"))
               if mi:
                   if mi.max_output:
      -                lines.append(f"Max output: {mi.max_output:,} tokens")
      +                lines.append(t("gateway.model.max_output_label", tokens=f"{mi.max_output:,}"))
                   if mi.has_cost_data():
      -                lines.append(f"Cost: {mi.format_cost()}")
      -            lines.append(f"Capabilities: {mi.format_capabilities()}")
      +                lines.append(t("gateway.model.cost_label", cost=mi.format_cost()))
      +            lines.append(t("gateway.model.capabilities_label", capabilities=mi.format_capabilities()))
       
               # Cache notice
               cache_enabled = (
      @@ -8588,15 +8588,15 @@ async def _on_model_selected(
                   or result.api_mode == "anthropic_messages"
               )
               if cache_enabled:
      -            lines.append("Prompt caching: enabled")
      +            lines.append(t("gateway.model.prompt_caching_enabled"))
       
               if result.warning_message:
      -            lines.append(f"Warning: {result.warning_message}")
      +            lines.append(t("gateway.model.warning_prefix", warning=result.warning_message))
       
               if persist_global:
      -            lines.append("Saved to config.yaml (`--global`)")
      +            lines.append(t("gateway.model.saved_global"))
               else:
      -            lines.append("_(session only -- add `--global` to persist)_")
      +            lines.append(t("gateway.model.session_only_hint"))
       
               return "\n".join(lines)
       
      @@ -8615,18 +8615,18 @@ async def _handle_personality_command(self, event: MessageEvent) -> str:
                   personalities = {}
       
               if not personalities:
      -            return f"No personalities configured in `{display_hermes_home()}/config.yaml`"
      +            return t("gateway.personality.none_configured", path=display_hermes_home())
       
               if not args:
      -            lines = ["🎭 **Available Personalities**\n"]
      -            lines.append("• `none` — (no personality overlay)")
      +            lines = [t("gateway.personality.header")]
      +            lines.append(t("gateway.personality.none_option"))
                   for name, prompt in personalities.items():
                       if isinstance(prompt, dict):
                           preview = prompt.get("description") or prompt.get("system_prompt", "")[:50]
                       else:
                           preview = prompt[:50] + "..." if len(prompt) > 50 else prompt
      -                lines.append(f"• `{name}` — {preview}")
      -            lines.append("\nUsage: `/personality <name>`")
      +                lines.append(t("gateway.personality.item", name=name, preview=preview))
      +            lines.append(t("gateway.personality.usage"))
                   return "\n".join(lines)
       
               def _resolve_prompt(value):
      @@ -8646,9 +8646,9 @@ def _resolve_prompt(value):
                       config["agent"]["system_prompt"] = ""
                       atomic_yaml_write(config_path, config)
                   except Exception as e:
      -                return f"⚠️ Failed to save personality change: {e}"
      +                return t("gateway.personality.save_failed", error=str(e))
                   self._ephemeral_system_prompt = ""
      -            return "🎭 Personality cleared — using base agent behavior.\n_(takes effect on next message)_"
      +            return t("gateway.personality.cleared")
               elif args in personalities:
                   new_prompt = _resolve_prompt(personalities[args])
       
      @@ -8659,15 +8659,15 @@ def _resolve_prompt(value):
                       config["agent"]["system_prompt"] = new_prompt
                       atomic_yaml_write(config_path, config)
                   except Exception as e:
      -                return f"⚠️ Failed to save personality change: {e}"
      +                return t("gateway.personality.save_failed", error=str(e))
       
                   # Update in-memory so it takes effect on the very next message.
                   self._ephemeral_system_prompt = new_prompt
       
      -            return f"🎭 Personality set to **{args}**\n_(takes effect on next message)_"
      +            return t("gateway.personality.set_to", name=args)
       
               available = "`none`, " + ", ".join(f"`{n}`" for n in personalities)
      -        return f"Unknown personality: `{args}`\n\nAvailable: {available}"
      +        return t("gateway.personality.unknown", name=args, available=available)
       
           async def _handle_retry_command(self, event: MessageEvent) -> str:
               """Handle /retry command - re-send the last user message."""
      @@ -8685,7 +8685,7 @@ async def _handle_retry_command(self, event: MessageEvent) -> str:
                       break
               
               if not last_user_msg:
      -            return "No previous message to retry."
      +            return t("gateway.retry.no_previous")
               
               # Truncate history to before the last user message and persist
               truncated = history[:last_user_idx]
      @@ -8767,7 +8767,7 @@ async def _handle_goal_command(self, event: "MessageEvent") -> str:
       
               mgr, session_entry = self._get_goal_manager_for_event(event)
               if mgr is None:
      -            return "Goals unavailable on this session."
      +            return t("gateway.goal.unavailable")
       
               if not args or lower == "status":
                   return mgr.status_line()
      @@ -8775,7 +8775,7 @@ async def _handle_goal_command(self, event: "MessageEvent") -> str:
               if lower == "pause":
                   state = mgr.pause(reason="user-paused")
                   if state is None:
      -                return "No goal set."
      +                return t("gateway.goal.no_goal_set")
                   try:
                       adapter = self.adapters.get(event.source.platform) if event.source else None
                       _quick_key = self._session_key_for_source(event.source) if event.source else None
      @@ -8783,16 +8783,13 @@ async def _handle_goal_command(self, event: "MessageEvent") -> str:
                           self._clear_goal_pending_continuations(_quick_key, adapter)
                   except Exception as exc:
                       logger.debug("goal pause: pending continuation cleanup failed: %s", exc)
      -            return f"⏸ Goal paused: {state.goal}"
      +            return t("gateway.goal.paused", goal=state.goal)
       
               if lower == "resume":
                   state = mgr.resume()
                   if state is None:
      -                return "No goal to resume."
      -            return (
      -                f"▶ Goal resumed: {state.goal}\n"
      -                "Send any message to continue, or wait — I'll take the next step on the next turn."
      -            )
      +                return t("gateway.goal.no_resume")
      +            return t("gateway.goal.resumed", goal=state.goal)
       
               if lower in ("clear", "stop", "done"):
                   had = mgr.has_goal()
      @@ -8810,7 +8807,7 @@ async def _handle_goal_command(self, event: "MessageEvent") -> str:
               try:
                   state = mgr.set(args)
               except ValueError as exc:
      -            return f"Invalid goal: {exc}"
      +            return t("gateway.goal.invalid", error=str(exc))
       
               # Queue the goal text as an immediate first turn so the agent
               # starts making progress. The post-turn hook takes over after.
      @@ -8829,11 +8826,7 @@ async def _handle_goal_command(self, event: "MessageEvent") -> str:
                   except Exception as exc:
                       logger.debug("goal kickoff enqueue failed: %s", exc)
       
      -        return (
      -            f"⊙ Goal set ({state.max_turns}-turn budget): {state.goal}\n"
      -            "I'll keep working until the goal is done, you pause/clear it, or the budget is exhausted.\n"
      -            "Controls: /goal status · /goal pause · /goal resume · /goal clear"
      -        )
      +        return t("gateway.goal.set", budget=state.max_turns, goal=state.goal)
       
           async def _send_goal_status_notice(self, source: Any, message: str) -> None:
               """Send a /goal judge status line back to the originating chat/thread."""
      @@ -8980,7 +8973,7 @@ async def _handle_undo_command(self, event: MessageEvent) -> str:
                       break
               
               if last_user_idx is None:
      -            return "Nothing to undo."
      +            return t("gateway.undo.nothing")
               
               removed_msg = history[last_user_idx].get("content", "")
               removed_count = len(history) - last_user_idx
      @@ -8989,7 +8982,7 @@ async def _handle_undo_command(self, event: MessageEvent) -> str:
               session_entry.last_prompt_tokens = 0
               
               preview = removed_msg[:40] + "..." if len(removed_msg) > 40 else removed_msg
      -        return f"↩️ Undid {removed_count} message(s).\nRemoved: \"{preview}\""
      +        return t("gateway.undo.removed", count=removed_count, preview=preview)
       
           async def _handle_set_home_command(self, event: MessageEvent) -> str:
               """Handle /sethome command -- set the current chat as the platform's home channel."""
      @@ -9010,7 +9003,7 @@ async def _handle_set_home_command(self, event: MessageEvent) -> str:
                   # /sethome is run from the parent chat instead of a thread.
                   save_env_value(thread_env_key, str(thread_id or ""))
               except Exception as e:
      -            return f"Failed to save home channel: {e}"
      +            return t("gateway.set_home.save_failed", error=e)
       
               # Keep the running gateway config in sync too. The pre-restart
               # notification path reads self.config before the process reloads env.
      @@ -9026,10 +9019,7 @@ async def _handle_set_home_command(self, event: MessageEvent) -> str:
                       thread_id=str(thread_id) if thread_id else None,
                   )
       
      -        return (
      -            f"✅ Home channel set to **{chat_name}** (ID: {chat_id}).\n"
      -            f"Cron jobs and cross-platform messages will be delivered here."
      -        )
      +        return t("gateway.set_home.success", name=chat_name, chat_id=chat_id)
       
           @staticmethod
           def _get_guild_id(event: MessageEvent) -> Optional[int]:
      @@ -9059,26 +9049,19 @@ async def _handle_voice_command(self, event: MessageEvent) -> str:
                   self._save_voice_modes()
                   if adapter:
                       self._set_adapter_auto_tts_enabled(adapter, chat_id, enabled=True)
      -            return (
      -                "Voice mode enabled.\n"
      -                "I'll reply with voice when you send voice messages.\n"
      -                "Use /voice tts to get voice replies for all messages."
      -            )
      +            return t("gateway.voice.enabled_voice_only")
               elif args in ("off", "disable"):
                   self._voice_mode[voice_key] = "off"
                   self._save_voice_modes()
                   if adapter:
                       self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=True)
      -            return "Voice mode disabled. Text-only replies."
      +            return t("gateway.voice.disabled_text")
               elif args == "tts":
                   self._voice_mode[voice_key] = "all"
                   self._save_voice_modes()
                   if adapter:
                       self._set_adapter_auto_tts_enabled(adapter, chat_id, enabled=True)
      -            return (
      -                "Auto-TTS enabled.\n"
      -                "All replies will include a voice message."
      -            )
      +            return t("gateway.voice.tts_enabled")
               elif args in ("channel", "join"):
                   return await self._handle_voice_channel_join(event)
               elif args == "leave":
      @@ -9086,9 +9069,9 @@ async def _handle_voice_command(self, event: MessageEvent) -> str:
               elif args == "status":
                   mode = self._voice_mode.get(voice_key, "off")
                   labels = {
      -                "off": "Off (text only)",
      -                "voice_only": "On (voice reply to voice messages)",
      -                "all": "TTS (voice reply to all messages)",
      +                "off": t("gateway.voice.label_off"),
      +                "voice_only": t("gateway.voice.label_voice_only"),
      +                "all": t("gateway.voice.label_all"),
                   }
                   # Append voice channel info if connected
                   adapter = self.adapters.get(event.source.platform)
      @@ -9097,15 +9080,15 @@ async def _handle_voice_command(self, event: MessageEvent) -> str:
                       info = adapter.get_voice_channel_info(guild_id)
                       if info:
                           lines = [
      -                        f"Voice mode: {labels.get(mode, mode)}",
      -                        f"Voice channel: #{info['channel_name']}",
      -                        f"Participants: {info['member_count']}",
      +                        t("gateway.voice.status_mode", label=labels.get(mode, mode)),
      +                        t("gateway.voice.status_channel", channel=info['channel_name']),
      +                        t("gateway.voice.status_participants", count=info['member_count']),
                           ]
                           for m in info["members"]:
      -                        status = " (speaking)" if m.get("is_speaking") else ""
      -                        lines.append(f"  - {m['display_name']}{status}")
      +                        status = t("gateway.voice.speaking") if m.get("is_speaking") else ""
      +                        lines.append(t("gateway.voice.status_member", name=m['display_name'], status=status))
                           return "\n".join(lines)
      -            return f"Voice mode: {labels.get(mode, mode)}"
      +            return t("gateway.voice.status_mode", label=labels.get(mode, mode))
               else:
                   # Toggle: off → on, on/all → off
                   current = self._voice_mode.get(voice_key, "off")
      @@ -9114,13 +9097,13 @@ async def _handle_voice_command(self, event: MessageEvent) -> str:
                       self._save_voice_modes()
                       if adapter:
                           self._set_adapter_auto_tts_enabled(adapter, chat_id, enabled=True)
      -                return "Voice mode enabled."
      +                return t("gateway.voice.enabled_short")
                   else:
                       self._voice_mode[voice_key] = "off"
                       self._save_voice_modes()
                       if adapter:
                           self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=True)
      -                return "Voice mode disabled."
      +                return t("gateway.voice.disabled_short")
       
           async def _handle_voice_channel_join(self, event: MessageEvent) -> str:
               """Join the user's current Discord voice channel."""
      @@ -9560,10 +9543,7 @@ async def _handle_rollback_command(self, event: MessageEvent) -> str:
                   pass
       
               if not cp_cfg.get("enabled", False):
      -            return (
      -                "Checkpoints are not enabled.\n"
      -                "Enable in config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      -            )
      +            return t("gateway.rollback.not_enabled")
       
               mgr = CheckpointManager(
                   enabled=True,
      @@ -9582,7 +9562,7 @@ async def _handle_rollback_command(self, event: MessageEvent) -> str:
               # Restore by number or hash
               checkpoints = mgr.list_checkpoints(cwd)
               if not checkpoints:
      -            return f"No checkpoints found for {cwd}"
      +            return t("gateway.rollback.none_found", cwd=cwd)
       
               target_hash = None
               try:
      @@ -9590,17 +9570,18 @@ async def _handle_rollback_command(self, event: MessageEvent) -> str:
                   if 0 <= idx < len(checkpoints):
                       target_hash = checkpoints[idx]["hash"]
                   else:
      -                return f"Invalid checkpoint number. Use 1-{len(checkpoints)}."
      +                return t("gateway.rollback.invalid_number", max=len(checkpoints))
               except ValueError:
                   target_hash = arg
       
               result = mgr.restore(cwd, target_hash)
               if result["success"]:
      -            return (
      -                f"✅ Restored to checkpoint {result['restored_to']}: {result['reason']}\n"
      -                f"A pre-rollback snapshot was saved automatically."
      +            return t(
      +                "gateway.rollback.restored",
      +                hash=result["restored_to"],
      +                reason=result["reason"],
                   )
      -        return f"❌ {result['error']}"
      +        return t("gateway.rollback.restore_failed", error=result["error"])
       
           async def _handle_background_command(self, event: MessageEvent) -> str:
               """Handle /background <prompt> — run a prompt in a separate background session.
      @@ -9611,12 +9592,7 @@ async def _handle_background_command(self, event: MessageEvent) -> str:
               """
               prompt = event.get_command_args().strip()
               if not prompt:
      -            return (
      -                "Usage: /background <prompt>\n"
      -                "Example: /background Summarize the top HN stories today\n\n"
      -                "Runs the prompt in a separate session. "
      -                "You can keep chatting — the result will appear here when done."
      -            )
      +            return t("gateway.background.usage")
       
               source = event.source
               task_id = f"bg_{datetime.now().strftime('%H%M%S')}_{os.urandom(3).hex()}"
      @@ -9636,7 +9612,7 @@ async def _handle_background_command(self, event: MessageEvent) -> str:
               _task.add_done_callback(self._background_tasks.discard)
       
               preview = prompt[:60] + ("..." if len(prompt) > 60 else "")
      -        return f'🔄 Background task started: "{preview}"\nTask ID: {task_id}\nYou can keep chatting — results will appear when done.'
      +        return t("gateway.background.started", preview=preview, task_id=task_id)
       
           async def _run_background_task(
               self,
      @@ -9835,20 +9811,27 @@ def _save_config_key(key_path: str, value):
                   # Show current state
                   rc = self._reasoning_config
                   if rc is None:
      -                level = "medium (default)"
      +                level = t("gateway.reasoning.level_default")
                   elif rc.get("enabled") is False:
      -                level = "none (disabled)"
      +                level = t("gateway.reasoning.level_disabled")
                   else:
                       level = rc.get("effort", "medium")
      -            display_state = "on ✓" if self._show_reasoning else "off"
      +            display_state = (
      +                t("gateway.reasoning.display_on")
      +                if self._show_reasoning
      +                else t("gateway.reasoning.display_off")
      +            )
                   has_session_override = session_key in (getattr(self, "_session_reasoning_overrides", {}) or {})
      -            scope = "session override" if has_session_override else "global config"
      -            return (
      -                "🧠 **Reasoning Settings**\n\n"
      -                f"**Effort:** `{level}`\n"
      -                f"**Scope:** {scope}\n"
      -                f"**Display:** {display_state}\n\n"
      -                "_Usage:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +            scope = (
      +                t("gateway.reasoning.scope_session")
      +                if has_session_override
      +                else t("gateway.reasoning.scope_global")
      +            )
      +            return t(
      +                "gateway.reasoning.status",
      +                level=level,
      +                scope=scope,
      +                display=display_state,
                   )
       
               # Display toggle (per-platform)
      @@ -9856,35 +9839,30 @@ def _save_config_key(key_path: str, value):
               if args in ("show", "on"):
                   self._show_reasoning = True
                   _save_config_key(f"display.platforms.{platform_key}.show_reasoning", True)
      -            return (
      -                "🧠 ✓ Reasoning display: **ON**\n"
      -                f"Model thinking will be shown before each response on **{platform_key}**."
      -            )
      +            return t("gateway.reasoning.display_set_on", platform=platform_key)
       
               if args in ("hide", "off"):
                   self._show_reasoning = False
                   _save_config_key(f"display.platforms.{platform_key}.show_reasoning", False)
      -            return f"🧠 ✓ Reasoning display: **OFF** for **{platform_key}**"
      +            return t("gateway.reasoning.display_set_off", platform=platform_key)
       
               # Effort level change
               effort = args.strip()
               if effort == "reset":
                   if persist_global:
      -                return "⚠️ `/reasoning reset --global` is not supported. Use `/reasoning <level> --global` to change the global default."
      +                return t("gateway.reasoning.reset_global_unsupported")
                   self._set_session_reasoning_override(session_key, None)
                   self._reasoning_config = self._load_reasoning_config()
                   self._evict_cached_agent(session_key)
      -            return "🧠 ✓ Session reasoning override cleared; falling back to global config."
      +            return t("gateway.reasoning.reset_done")
               if effort == "none":
                   parsed = {"enabled": False}
               elif effort in ("minimal", "low", "medium", "high", "xhigh"):
                   parsed = {"enabled": True, "effort": effort}
               else:
      -            return (
      -                f"⚠️ Unknown argument: `{effort or raw_args.lower()}`\n\n"
      -                "**Valid levels:** none, minimal, low, medium, high, xhigh\n"
      -                "**Display:** show, hide\n"
      -                "**Persist:** add `--global` to save beyond this session"
      +            return t(
      +                "gateway.reasoning.unknown_arg",
      +                arg=effort or raw_args.lower(),
                   )
       
               self._reasoning_config = parsed
      @@ -9892,14 +9870,14 @@ def _save_config_key(key_path: str, value):
                   if _save_config_key("agent.reasoning_effort", effort):
                       self._set_session_reasoning_override(session_key, None)
                       self._evict_cached_agent(session_key)
      -                return f"🧠 ✓ Reasoning effort set to `{effort}` (saved to config)\n_(takes effect on next message)_"
      +                return t("gateway.reasoning.set_global", effort=effort)
                   self._set_session_reasoning_override(session_key, parsed)
                   self._evict_cached_agent(session_key)
      -            return f"🧠 ✓ Reasoning effort set to `{effort}` (session only — config save failed)\n_(takes effect on next message)_"
      +            return t("gateway.reasoning.set_global_save_failed", effort=effort)
       
               self._set_session_reasoning_override(session_key, parsed)
               self._evict_cached_agent(session_key)
      -        return f"🧠 ✓ Reasoning effort set to `{effort}` (session only — add `--global` to persist)\n_(takes effect on next message)_"
      +        return t("gateway.reasoning.set_session", effort=effort)
       
           async def _handle_fast_command(self, event: MessageEvent) -> str:
               """Handle /fast — mirror the CLI Priority Processing toggle in gateway chats."""
      @@ -9913,7 +9891,7 @@ async def _handle_fast_command(self, event: MessageEvent) -> str:
               user_config = _load_gateway_config()
               model = _resolve_gateway_model(user_config)
               if not model_supports_fast_mode(model):
      -            return "⚡ /fast is only available for OpenAI models that support Priority Processing."
      +            return t("gateway.fast.not_supported")
       
               def _save_config_key(key_path: str, value):
                   """Save a dot-separated key to config.yaml."""
      @@ -9936,30 +9914,23 @@ def _save_config_key(key_path: str, value):
                       return False
       
               if not args or args == "status":
      -            status = "fast" if self._service_tier == "priority" else "normal"
      -            return (
      -                "⚡ Priority Processing\n\n"
      -                f"Current mode: `{status}`\n\n"
      -                "_Usage:_ `/fast <normal|fast|status>`"
      -            )
      +            status = t("gateway.fast.status_fast") if self._service_tier == "priority" else t("gateway.fast.status_normal")
      +            return t("gateway.fast.status", mode=status)
       
               if args in {"fast", "on"}:
                   self._service_tier = "priority"
                   saved_value = "fast"
      -            label = "FAST"
      +            label = t("gateway.fast.label_fast")
               elif args in {"normal", "off"}:
                   self._service_tier = None
                   saved_value = "normal"
      -            label = "NORMAL"
      +            label = t("gateway.fast.label_normal")
               else:
      -            return (
      -                f"⚠️ Unknown argument: `{args}`\n\n"
      -                "**Valid options:** normal, fast, status"
      -            )
      +            return t("gateway.fast.unknown_arg", arg=args)
       
               if _save_config_key("agent.service_tier", saved_value):
      -            return f"⚡ ✓ Priority Processing: **{label}** (saved to config)\n_(takes effect on next message)_"
      -        return f"⚡ ✓ Priority Processing: **{label}** (this session only)"
      +            return t("gateway.fast.saved", label=label)
      +        return t("gateway.fast.session_only", label=label)
       
           async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
               """Handle /yolo — toggle dangerous command approval bypass for this session only."""
      @@ -9973,10 +9944,10 @@ async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, Ephemera
               current = is_session_yolo_enabled(session_key)
               if current:
                   disable_session_yolo(session_key)
      -            return EphemeralReply("⚠️ YOLO mode **OFF** for this session — dangerous commands will require approval.")
      +            return EphemeralReply(t("gateway.yolo.disabled"))
               else:
                   enable_session_yolo(session_key)
      -            return EphemeralReply("⚡ YOLO mode **ON** for this session — all commands auto-approved. Use with caution.")
      +            return EphemeralReply(t("gateway.yolo.enabled"))
       
           async def _handle_verbose_command(self, event: MessageEvent) -> str:
               """Handle /verbose command — cycle tool progress display mode.
      @@ -10002,19 +9973,15 @@ async def _handle_verbose_command(self, event: MessageEvent) -> str:
                   gate_enabled = False
       
               if not gate_enabled:
      -            return (
      -                "The `/verbose` command is not enabled for messaging platforms.\n\n"
      -                "Enable it in `config.yaml`:\n```yaml\n"
      -                "display:\n  tool_progress_command: true\n```"
      -            )
      +            return t("gateway.verbose.not_enabled")
       
               # --- cycle mode (per-platform) ----------------------------------------
               cycle = ["off", "new", "all", "verbose"]
               descriptions = {
      -            "off": "⚙️ Tool progress: **OFF** — no tool activity shown.",
      -            "new": "⚙️ Tool progress: **NEW** — shown when tool changes (preview length: `display.tool_preview_length`, default 40).",
      -            "all": "⚙️ Tool progress: **ALL** — every tool call shown (preview length: `display.tool_preview_length`, default 40).",
      -            "verbose": "⚙️ Tool progress: **VERBOSE** — every tool call with full arguments.",
      +            "off": t("gateway.verbose.mode_off"),
      +            "new": t("gateway.verbose.mode_new"),
      +            "all": t("gateway.verbose.mode_all"),
      +            "verbose": t("gateway.verbose.mode_verbose"),
               }
       
               # Read current effective mode for this platform via the resolver
      @@ -10038,11 +10005,11 @@ async def _handle_verbose_command(self, event: MessageEvent) -> str:
                   atomic_yaml_write(config_path, user_config)
                   return (
                       f"{descriptions[new_mode]}\n"
      -                f"_(saved for **{platform_key}** — takes effect on next message)_"
      +                + t("gateway.verbose.saved_suffix", platform=platform_key)
                   )
               except Exception as e:
                   logger.warning("Failed to save tool_progress mode: %s", e)
      -            return f"{descriptions[new_mode]}\n_(could not save to config: {e})_"
      +            return f"{descriptions[new_mode]}\n" + t("gateway.verbose.save_failed", error=e)
       
           async def _handle_footer_command(self, event: MessageEvent) -> str:
               """Handle /footer command — toggle the runtime-metadata footer.
      @@ -10083,12 +10050,13 @@ async def _handle_footer_command(self, event: MessageEvent) -> str:
               effective = resolve_footer_config(user_config, platform_key)
       
               if arg in ("status", "?"):
      -            state = "ON" if effective["enabled"] else "OFF"
      +            state = t("gateway.footer.state_on") if effective["enabled"] else t("gateway.footer.state_off")
                   fields = ", ".join(effective.get("fields") or [])
      -            return (
      -                f"📎 Runtime footer: **{state}**\n"
      -                f"Fields: `{fields}`\n"
      -                f"Platform: `{platform_key}`"
      +            return t(
      +                "gateway.footer.status",
      +                state=state,
      +                fields=fields,
      +                platform=platform_key,
                   )
       
               if arg in ("on", "enable", "true", "1"):
      @@ -10098,7 +10066,7 @@ async def _handle_footer_command(self, event: MessageEvent) -> str:
               elif arg == "":
                   new_state = not effective["enabled"]
               else:
      -            return "Usage: `/footer [on|off|status]`"
      +            return t("gateway.footer.usage")
       
               # --- write global flag ---------------------------------------------
               try:
      @@ -10113,7 +10081,7 @@ async def _handle_footer_command(self, event: MessageEvent) -> str:
                   logger.warning("Failed to save runtime_footer.enabled: %s", e)
                   return t("gateway.config_save_failed", error=e)
       
      -        state = "ON" if new_state else "OFF"
      +        state = t("gateway.footer.state_on") if new_state else t("gateway.footer.state_off")
               example = ""
               if new_state:
                   # Show a preview using current agent state if available.
      @@ -10125,12 +10093,8 @@ async def _handle_footer_command(self, event: MessageEvent) -> str:
                       fields=effective.get("fields") or ["model", "context_pct", "cwd"],
                   )
                   if preview:
      -                example = f"\nExample: `{preview}`"
      -        return (
      -            f"📎 Runtime footer: **{state}**"
      -            f"{example}\n"
      -            f"_(saved globally — takes effect on next message)_"
      -        )
      +                example = t("gateway.footer.example_line", preview=preview)
      +        return t("gateway.footer.saved", state=state, example=example)
       
           async def _handle_compress_command(self, event: MessageEvent) -> str:
               """Handle /compress command -- manually compress conversation context.
      @@ -10144,7 +10108,7 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
               history = self.session_store.load_transcript(session_entry.session_id)
       
               if not history or len(history) < 4:
      -            return "Not enough conversation to compress (need at least 4 messages)."
      +            return t("gateway.compress.not_enough")
       
               # Extract optional focus topic from command args
               focus_topic = (event.get_command_args() or "").strip() or None
      @@ -10160,7 +10124,7 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
                       session_key=session_key,
                   )
                   if not runtime_kwargs.get("api_key"):
      -                return "No provider configured -- cannot compress."
      +                return t("gateway.compress.no_provider")
       
                   msgs = [
                       {"role": m.get("role"), "content": m.get("content")}
      @@ -10192,7 +10156,7 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
       
                       compressor = tmp_agent.context_compressor
                       if not compressor.has_content_to_compress(msgs):
      -                    return "Nothing to compress yet (the transcript is still all protected context)."
      +                    return t("gateway.compress.nothing_to_do")
       
                       loop = asyncio.get_running_loop()
                       compressed, _ = await loop.run_in_executor(
      @@ -10241,28 +10205,30 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
                       self._cleanup_agent_resources(tmp_agent)
                   lines = [f"🗜️ {summary['headline']}"]
                   if focus_topic:
      -                lines.append(f"Focus: \"{focus_topic}\"")
      +                lines.append(t("gateway.compress.focus_line", topic=focus_topic))
                   lines.append(summary["token_line"])
                   if summary["note"]:
                       lines.append(summary["note"])
                   if _summary_failed:
                       lines.append(
      -                    f"⚠️ Summary generation failed ({_summary_err or 'unknown error'}). "
      -                    f"{_dropped_count} historical message(s) were removed and replaced "
      -                    "with a placeholder; earlier context is no longer recoverable. "
      -                    "Consider checking your auxiliary.compression model configuration."
      +                    t(
      +                        "gateway.compress.summary_failed",
      +                        error=(_summary_err or "unknown error"),
      +                        count=_dropped_count,
      +                    )
                       )
                   elif _aux_fail_model:
                       lines.append(
      -                    f"ℹ️ Configured compression model `{_aux_fail_model}` failed "
      -                    f"({_aux_fail_err or 'unknown error'}). Recovered using your main "
      -                    "model — context is intact — but you may want to check "
      -                    "`auxiliary.compression.model` in config.yaml."
      +                    t(
      +                        "gateway.compress.aux_failed",
      +                        model=_aux_fail_model,
      +                        error=(_aux_fail_err or "unknown error"),
      +                    )
                       )
                   return "\n".join(lines)
               except Exception as e:
                   logger.warning("Manual compress failed: %s", e)
      -            return f"Compression failed: {e}"
      +            return t("gateway.compress.failed", error=e)
       
           async def _get_telegram_topic_capabilities(self, source: SessionSource) -> dict:
               """Read Telegram private-topic capability flags via Bot API getMe."""
      @@ -10518,7 +10484,7 @@ def _disable_telegram_topic_mode_for_chat(self, source: SessionSource) -> str:
               """Cleanly disable topic mode for a chat via /topic off."""
               if not self._session_db:
                   from hermes_state import format_session_db_unavailable
      -            return format_session_db_unavailable()
      +            return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
               chat_id = str(source.chat_id or "")
               if not chat_id:
                   return "Could not determine chat ID."
      @@ -10554,10 +10520,10 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
               """Handle /topic for Telegram DM user-managed topic sessions."""
               source = event.source
               if source.platform != Platform.TELEGRAM or source.chat_type != "dm":
      -            return "The /topic command is only available in Telegram private chats."
      +            return t("gateway.topic.not_telegram_dm")
               if not self._session_db:
                   from hermes_state import format_session_db_unavailable
      -            return format_session_db_unavailable()
      +            return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
       
               # Authorization: /topic activates multi-session mode and mutates
               # SQLite side tables. Unauthorized senders (not in allowlist) must
      @@ -10567,7 +10533,7 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
               if callable(auth_fn):
                   try:
                       if not auth_fn(source):
      -                    return "You are not authorized to use /topic on this bot."
      +                    return t("gateway.topic.unauthorized")
                   except Exception:
                       logger.debug("Topic auth check failed", exc_info=True)
       
      @@ -10583,11 +10549,7 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
       
               if args:
                   if not source.thread_id:
      -                return (
      -                    "To restore a session, first create or open a Telegram topic, "
      -                    "then send /topic <session-id> inside that topic. To create a "
      -                    "new topic, open All Messages and send any message there."
      -                )
      +                return t("gateway.topic.restore_needs_topic")
                   return await self._restore_telegram_topic_session(event, args)
       
               capabilities = await self._get_telegram_topic_capabilities(source)
      @@ -10597,24 +10559,11 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
                       # /topic while threads are still disabled.
                       if self._should_send_telegram_capability_hint(source):
                           await self._send_telegram_topic_setup_image(source)
      -                return (
      -                    "Telegram topics are not enabled for this bot yet.\n\n"
      -                    "How to enable them:\n"
      -                    "1. Open @BotFather.\n"
      -                    "2. Choose your bot.\n"
      -                    "3. Open Bot Settings → Threads Settings.\n"
      -                    "4. Turn on Threaded Mode and make sure users are allowed to create new threads.\n\n"
      -                    "Then send /topic again."
      -                )
      +                return t("gateway.topic.topics_disabled")
                   if capabilities.get("allows_users_to_create_topics") is False:
                       if self._should_send_telegram_capability_hint(source):
                           await self._send_telegram_topic_setup_image(source)
      -                return (
      -                    "Telegram topics are enabled, but users are not allowed to create topics.\n\n"
      -                    "Open @BotFather → choose your bot → Bot Settings → Threads Settings, "
      -                    "then turn off 'Disallow users to create new threads'.\n\n"
      -                    "Then send /topic again."
      -                )
      +                return t("gateway.topic.topics_user_disallowed")
       
               try:
                   self._session_db.enable_telegram_topic_mode(
      @@ -10625,7 +10574,7 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
                   )
               except Exception as exc:
                   logger.exception("Failed to enable Telegram topic mode")
      -            return f"Failed to enable Telegram topic mode: {exc}"
      +            return t("gateway.topic.enable_failed", error=exc)
       
               if not source.thread_id:
                   await self._ensure_telegram_system_topic(source)
      @@ -10646,21 +10595,13 @@ async def _handle_topic_command(self, event: MessageEvent, args: str = "") -> st
                           title = self._session_db.get_session_title(session_id)
                       except Exception:
                           title = None
      -                session_label = title or "Untitled session"
      -                return (
      -                    "This topic is linked to:\n"
      -                    f"Session: {session_label}\n"
      -                    f"ID: {session_id}\n\n"
      -                    "Use /new to replace this topic with a fresh session.\n"
      -                    "For parallel work, open All Messages and send a message there "
      -                    "to create another topic."
      +                session_label = title or t("gateway.topic.untitled_session")
      +                return t(
      +                    "gateway.topic.bound_status",
      +                    label=session_label,
      +                    session_id=session_id,
                       )
      -            return (
      -                "Telegram multi-session topics are enabled.\n\n"
      -                "This topic will be used as an independent Hermes session. "
      -                "Use /new to replace this topic's current session. For parallel "
      -                "work, open All Messages and send a message there to create another topic."
      -            )
      +            return t("gateway.topic.thread_ready")
       
               return self._telegram_topic_root_status_message(source)
       
      @@ -10772,7 +10713,7 @@ async def _handle_title_command(self, event: MessageEvent) -> str:
       
               if not self._session_db:
                   from hermes_state import format_session_db_unavailable
      -            return format_session_db_unavailable()
      +            return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
       
               # Ensure session exists in SQLite DB (it may only exist in session_store
               # if this is the first command in a new session)
      @@ -10794,30 +10735,30 @@ async def _handle_title_command(self, event: MessageEvent) -> str:
                   try:
                       sanitized = self._session_db.sanitize_title(title_arg)
                   except ValueError as e:
      -                return f"⚠️ {e}"
      +                return t("gateway.shared.warn_passthrough", error=e)
                   if not sanitized:
      -                return "⚠️ Title is empty after cleanup. Please use printable characters."
      +                return t("gateway.title.empty_after_clean")
                   # Set the title
                   try:
                       if self._session_db.set_session_title(session_id, sanitized):
      -                    return f"✏️ Session title set: **{sanitized}**"
      +                    return t("gateway.title.set_to", title=sanitized)
                       else:
      -                    return "Session not found in database."
      +                    return t("gateway.title.not_found")
                   except ValueError as e:
      -                return f"⚠️ {e}"
      +                return t("gateway.shared.warn_passthrough", error=e)
               else:
                   # Show the current title and session ID
                   title = self._session_db.get_session_title(session_id)
                   if title:
      -                return f"📌 Session: `{session_id}`\nTitle: **{title}**"
      +                return t("gateway.title.current_with_title", session_id=session_id, title=title)
                   else:
      -                return f"📌 Session: `{session_id}`\nNo title set. Usage: `/title My Session Name`"
      +                return t("gateway.title.current_no_title", session_id=session_id)
       
           async def _handle_resume_command(self, event: MessageEvent) -> str:
               """Handle /resume command — switch to a previously-named session."""
               if not self._session_db:
                   from hermes_state import format_session_db_unavailable
      -            return format_session_db_unavailable()
      +            return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
       
               source = event.source
               session_key = self._session_key_for_source(source)
      @@ -10832,30 +10773,23 @@ async def _handle_resume_command(self, event: MessageEvent) -> str:
                       )
                       titled = [s for s in sessions if s.get("title")]
                       if not titled:
      -                    return (
      -                        "No named sessions found.\n"
      -                        "Use `/title My Session` to name your current session, "
      -                        "then `/resume My Session` to return to it later."
      -                    )
      -                lines = ["📋 **Named Sessions**\n"]
      +                    return t("gateway.resume.no_named_sessions")
      +                lines = [t("gateway.resume.list_header")]
                       for s in titled[:10]:
                           title = s["title"]
                           preview = s.get("preview", "")[:40]
      -                    preview_part = f" — _{preview}_" if preview else ""
      -                    lines.append(f"• **{title}**{preview_part}")
      -                lines.append("\nUsage: `/resume <session name>`")
      +                    preview_part = t("gateway.resume.list_preview_suffix", preview=preview) if preview else ""
      +                    lines.append(t("gateway.resume.list_item", title=title, preview_part=preview_part))
      +                lines.append(t("gateway.resume.list_footer"))
                       return "\n".join(lines)
                   except Exception as e:
                       logger.debug("Failed to list titled sessions: %s", e)
      -                return f"Could not list sessions: {e}"
      +                return t("gateway.resume.list_failed", error=e)
       
               # Resolve the name to a session ID.
               target_id = self._session_db.resolve_session_by_title(name)
               if not target_id:
      -            return (
      -                f"No session found matching '**{name}**'.\n"
      -                "Use `/resume` with no arguments to see available sessions."
      -            )
      +            return t("gateway.resume.not_found", name=name)
               # Compression creates child continuations that hold the live transcript.
               # Follow that chain so gateway /resume matches CLI behavior (#15000).
               try:
      @@ -10866,7 +10800,7 @@ async def _handle_resume_command(self, event: MessageEvent) -> str:
               # Check if already on that session
               current_entry = self.session_store.get_or_create_session(source)
               if current_entry.session_id == target_id:
      -            return f"📌 Already on session **{name}**."
      +            return t("gateway.resume.already_on", name=name)
       
               # Clear any running agent for this session key
               self._release_running_agent_state(session_key)
      @@ -10874,7 +10808,7 @@ async def _handle_resume_command(self, event: MessageEvent) -> str:
               # Switch the session entry to point at the old session
               new_entry = self.session_store.switch_session(session_key, target_id)
               if not new_entry:
      -            return "Failed to switch session."
      +            return t("gateway.resume.switch_failed")
               self._clear_session_boundary_security_state(session_key)
       
               # Evict any cached agent for this session so the next message
      @@ -10890,9 +10824,11 @@ async def _handle_resume_command(self, event: MessageEvent) -> str:
               # Count messages for context
               history = self.session_store.load_transcript(target_id)
               msg_count = len([m for m in history if m.get("role") == "user"]) if history else 0
      -        msg_part = f" ({msg_count} message{'s' if msg_count != 1 else ''})" if msg_count else ""
      -
      -        return f"↻ Resumed session **{title}**{msg_part}. Conversation restored."
      +        if not msg_count:
      +            return t("gateway.resume.resumed_no_count", title=title)
      +        if msg_count == 1:
      +            return t("gateway.resume.resumed_one", title=title, count=msg_count)
      +        return t("gateway.resume.resumed_many", title=title, count=msg_count)
       
           async def _handle_branch_command(self, event: MessageEvent) -> str:
               """Handle /branch [name] — fork the current session into a new independent copy.
      @@ -10905,7 +10841,7 @@ async def _handle_branch_command(self, event: MessageEvent) -> str:
       
               if not self._session_db:
                   from hermes_state import format_session_db_unavailable
      -            return format_session_db_unavailable()
      +            return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
       
               source = event.source
               session_key = self._session_key_for_source(source)
      @@ -10914,7 +10850,7 @@ async def _handle_branch_command(self, event: MessageEvent) -> str:
               current_entry = self.session_store.get_or_create_session(source)
               history = self.session_store.load_transcript(current_entry.session_id)
               if not history:
      -            return "No conversation to branch — send a message first."
      +            return t("gateway.branch.no_conversation")
       
               branch_name = event.get_command_args().strip()
       
      @@ -10945,7 +10881,7 @@ async def _handle_branch_command(self, event: MessageEvent) -> str:
                   )
               except Exception as e:
                   logger.error("Failed to create branch session: %s", e)
      -            return f"Failed to create branch: {e}"
      +            return t("gateway.branch.create_failed", error=e)
       
               # Copy conversation history to the new session
               for msg in history:
      @@ -10976,20 +10912,15 @@ async def _handle_branch_command(self, event: MessageEvent) -> str:
               # Switch the session store entry to the new session
               new_entry = self.session_store.switch_session(session_key, new_session_id)
               if not new_entry:
      -            return "Branch created but failed to switch to it."
      +            return t("gateway.branch.switch_failed")
               self._clear_session_boundary_security_state(session_key)
       
               # Evict any cached agent for this session
               self._evict_cached_agent(session_key)
       
               msg_count = len([m for m in history if m.get("role") == "user"])
      -        return (
      -            f"⑂ Branched to **{branch_title}**"
      -            f" ({msg_count} message{'s' if msg_count != 1 else ''} copied)\n"
      -            f"Original: `{parent_session_id}`\n"
      -            f"Branch: `{new_session_id}`\n"
      -            f"Use `/resume` to switch back to the original."
      -        )
      +        key = "gateway.branch.branched_one" if msg_count == 1 else "gateway.branch.branched_many"
      +        return t(key, title=branch_title, count=msg_count, parent=parent_session_id, new=new_session_id)
       
           async def _handle_usage_command(self, event: MessageEvent) -> str:
               """Handle /usage command -- show token usage for the current session.
      @@ -11051,7 +10982,7 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                   rl_state = agent.get_rate_limit_state()
                   if rl_state and rl_state.has_data:
                       from agent.rate_limit_tracker import format_rate_limit_compact
      -                lines.append(f"⏱️ **Rate Limits:** {format_rate_limit_compact(rl_state)}")
      +                lines.append(t("gateway.usage.rate_limits", state=format_rate_limit_compact(rl_state)))
                       lines.append("")
       
                   # Session token usage — detailed breakdown matching CLI
      @@ -11060,16 +10991,16 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                   cache_read = getattr(agent, "session_cache_read_tokens", 0) or 0
                   cache_write = getattr(agent, "session_cache_write_tokens", 0) or 0
       
      -            lines.append("📊 **Session Token Usage**")
      -            lines.append(f"Model: `{agent.model}`")
      -            lines.append(f"Input tokens: [redacted:secret-assignment]")
      +            lines.append(t("gateway.usage.header_session"))
      +            lines.append(t("gateway.usage.label_model", model=agent.model))
      +            lines.append(t("gateway.usage.label_input_tokens", count=f"{input_tokens:,}"))
                   if cache_read:
      -                lines.append(f"Cache read tokens: [redacted:secret-assignment]")
      +                lines.append(t("gateway.usage.label_cache_read", count=f"{cache_read:,}"))
                   if cache_write:
      -                lines.append(f"Cache write tokens: [redacted:secret-assignment]")
      -            lines.append(f"Output tokens: [redacted:secret-assignment]")
      -            lines.append(f"Total: {agent.session_total_tokens:,}")
      -            lines.append(f"API calls: {agent.session_api_calls}")
      +                lines.append(t("gateway.usage.label_cache_write", count=f"{cache_write:,}"))
      +            lines.append(t("gateway.usage.label_output_tokens", count=f"{output_tokens:,}"))
      +            lines.append(t("gateway.usage.label_total", count=f"{agent.session_total_tokens:,}"))
      +            lines.append(t("gateway.usage.label_api_calls", count=agent.session_api_calls))
       
                   # Cost estimation
                   try:
      @@ -11087,9 +11018,9 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                       )
                       if cost_result.amount_usd is not None:
                           prefix = "~" if cost_result.status == "estimated" else ""
      -                    lines.append(f"Cost: {prefix}${float(cost_result.amount_usd):.4f}")
      +                    lines.append(t("gateway.usage.label_cost", prefix=prefix, amount=f"{float(cost_result.amount_usd):.4f}"))
                       elif cost_result.status == "included":
      -                    lines.append("Cost: included")
      +                    lines.append(t("gateway.usage.label_cost_included"))
                   except Exception:
                       pass
       
      @@ -11097,9 +11028,9 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                   ctx = agent.context_compressor
                   if ctx.last_prompt_tokens:
                       pct = min(100, ctx.last_prompt_tokens / ctx.context_length * 100) if ctx.context_length else 0
      -                lines.append(f"Context: {ctx.last_prompt_tokens:,} / {ctx.context_length:,} ({pct:.0f}%)")
      +                lines.append(t("gateway.usage.label_context", used=f"{ctx.last_prompt_tokens:,}", total=f"{ctx.context_length:,}", pct=f"{pct:.0f}"))
                   if ctx.compression_count:
      -                lines.append(f"Compressions: {ctx.compression_count}")
      +                lines.append(t("gateway.usage.label_compressions", count=ctx.compression_count))
       
                   if account_lines:
                       lines.append("")
      @@ -11115,10 +11046,10 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                   msgs = [m for m in history if m.get("role") in ("user", "assistant") and m.get("content")]
                   approx = estimate_messages_tokens_rough(msgs)
                   lines = [
      -                "📊 **Session Info**",
      -                f"Messages: {len(msgs)}",
      -                f"Estimated context: ~{approx:,} tokens",
      -                "_(Detailed usage available after the first agent response)_",
      +                t("gateway.usage.header_session_info"),
      +                t("gateway.usage.label_messages", count=len(msgs)),
      +                t("gateway.usage.label_estimated_context", count=f"{approx:,}"),
      +                t("gateway.usage.detailed_after_first"),
                   ]
                   if account_lines:
                       lines.append("")
      @@ -11126,7 +11057,7 @@ async def _handle_usage_command(self, event: MessageEvent) -> str:
                   return "\n".join(lines)
               if account_lines:
                   return "\n".join(account_lines)
      -        return "No usage data available for this session."
      +        return t("gateway.usage.no_data")
       
           async def _handle_insights_command(self, event: MessageEvent) -> str:
               """Handle /insights command -- show usage insights and analytics."""
      @@ -11147,7 +11078,7 @@ async def _handle_insights_command(self, event: MessageEvent) -> str:
                           try:
                               days = int(parts[i + 1])
                           except ValueError:
      -                        return f"Invalid --days value: {parts[i + 1]}"
      +                        return t("gateway.insights.invalid_days", value=parts[i + 1])
                           i += 2
                       elif parts[i] == "--source" and i + 1 < len(parts):
                           source = parts[i + 1]
      @@ -11175,7 +11106,7 @@ def _run_insights():
                   return await loop.run_in_executor(None, _run_insights)
               except Exception as e:
                   logger.error("Insights command error: %s", e, exc_info=True)
      -            return f"Error generating insights: {e}"
      +            return t("gateway.insights.error", error=e)
       
           async def _handle_reload_mcp_command(self, event: MessageEvent) -> Optional[str]:
               """Handle /reload-mcp — reconnect MCP servers and rebuild the cached agent.
      @@ -11213,7 +11144,7 @@ async def _handle_reload_mcp_command(self, event: MessageEvent) -> Optional[str]
               # chosen outcome.
               async def _on_confirm(choice: str) -> Optional[str]:
                   if choice == "cancel":
      -                return "🟡 /reload-mcp cancelled. MCP tools unchanged."
      +                return t("gateway.reload_mcp.cancelled")
                   if choice == "always":
                       # Persist the opt-out and run the reload.
                       try:
      @@ -11228,25 +11159,10 @@ async def _on_confirm(choice: str) -> Optional[str]:
                   # once / always → run the reload
                   result = await self._execute_mcp_reload(event)
                   if choice == "always":
      -                return (
      -                    f"{result}\n\n"
      -                    "ℹ️ Future `/reload-mcp` calls will run without confirmation. "
      -                    "Re-enable via `approvals.mcp_reload_confirm: true` in config.yaml."
      -                )
      +                return f"{result}\n\n" + t("gateway.reload_mcp.always_followup")
                   return result
       
      -        prompt_message = (
      -            "⚠️ **Confirm /reload-mcp**\n\n"
      -            "Reloading MCP servers rebuilds the tool set for this session "
      -            "and **invalidates the provider prompt cache** — the next "
      -            "message will re-send full input tokens.  On long-context or "
      -            "high-reasoning models this can be expensive.\n\n"
      -            "Choose:\n"
      -            "• **Approve Once** — reload now\n"
      -            "• **Always Approve** — reload now and silence this prompt permanently\n"
      -            "• **Cancel** — leave MCP tools unchanged\n\n"
      -            "_Text fallback: reply `/approve`, `/always`, or `/cancel`._"
      -        )
      +        prompt_message = t("gateway.reload_mcp.confirm_prompt")
               return await self._request_slash_confirm(
                   event=event,
                   command="reload-mcp",
      @@ -11285,17 +11201,17 @@ async def _execute_mcp_reload(self, event: MessageEvent) -> str:
                   removed = old_servers - connected_servers
                   reconnected = connected_servers & old_servers
       
      -            lines = ["🔄 **MCP Servers Reloaded**\n"]
      +            lines = [t("gateway.reload_mcp.header")]
                   if reconnected:
      -                lines.append(f"♻️ Reconnected: {', '.join(sorted(reconnected))}")
      +                lines.append(t("gateway.reload_mcp.reconnected", names=", ".join(sorted(reconnected))))
                   if added:
      -                lines.append(f"➕ Added: {', '.join(sorted(added))}")
      +                lines.append(t("gateway.reload_mcp.added", names=", ".join(sorted(added))))
                   if removed:
      -                lines.append(f"➖ Removed: {', '.join(sorted(removed))}")
      +                lines.append(t("gateway.reload_mcp.removed", names=", ".join(sorted(removed))))
                   if not connected_servers:
      -                lines.append("No MCP servers connected.")
      +                lines.append(t("gateway.reload_mcp.none_connected"))
                   else:
      -                lines.append(f"\n🔧 {len(new_tools)} tool(s) available from {len(connected_servers)} server(s)")
      +                lines.append(t("gateway.reload_mcp.tools_available", tools=len(new_tools), servers=len(connected_servers)))
       
                   # Inject a message at the END of the session history so the
                   # model knows tools changed on its next turn.  Appended after
      @@ -11325,7 +11241,7 @@ async def _execute_mcp_reload(self, event: MessageEvent) -> str:
       
               except Exception as e:
                   logger.warning("MCP reload failed: %s", e)
      -            return f"❌ MCP reload failed: {e}"
      +            return t("gateway.reload_mcp.failed", error=e)
       
           async def _handle_reload_skills_command(self, event: MessageEvent) -> str:
               """Handle /reload-skills — rescan skills dir, queue a note for next turn.
      @@ -11373,26 +11289,28 @@ async def _handle_reload_skills_command(self, event: MessageEvent) -> str:
                               getattr(adapter, "name", adapter), exc,
                           )
       
      -            lines = ["🔄 **Skills Reloaded**\n"]
      +            lines = [t("gateway.reload_skills.header")]
                   if not added and not removed:
      -                lines.append("No new skills detected.")
      -                lines.append(f"\n📚 {total} skill(s) available")
      +                lines.append(t("gateway.reload_skills.no_new"))
      +                lines.append(t("gateway.reload_skills.total", count=total))
                       return "\n".join(lines)
       
                   def _fmt_line(item: dict) -> str:
                       nm = item.get("name", "")
                       desc = item.get("description", "")
      -                return f"    - {nm}: {desc}" if desc else f"    - {nm}"
      +                if desc:
      +                    return t("gateway.reload_skills.item_with_desc", name=nm, desc=desc)
      +                return t("gateway.reload_skills.item_no_desc", name=nm)
       
                   if added:
      -                lines.append("➕ **Added Skills:**")
      +                lines.append(t("gateway.reload_skills.added_header"))
                       for item in added:
                           lines.append(_fmt_line(item))
                   if removed:
      -                lines.append("➖ **Removed Skills:**")
      +                lines.append(t("gateway.reload_skills.removed_header"))
                       for item in removed:
                           lines.append(_fmt_line(item))
      -            lines.append(f"\n📚 {total} skill(s) available")
      +            lines.append(t("gateway.reload_skills.total", count=total))
       
                   # Queue the one-shot note for the next user turn in this session.
                   # Format matches how the system prompt renders pre-existing
      @@ -11423,7 +11341,7 @@ def _fmt_line(item: dict) -> str:
       
               except Exception as e:
                   logger.warning("Skills reload failed: %s", e)
      -            return f"❌ Skills reload failed: {e}"
      +            return t("gateway.reload_skills.failed", error=e)
       
           # ------------------------------------------------------------------
           # Slash-command confirmation primitive (generic)
      @@ -11672,7 +11590,7 @@ async def _handle_approve_command(self, event: MessageEvent) -> Optional[str]:
                   if session_key in self._pending_approvals:
                       self._pending_approvals.pop(session_key)
                       return t("gateway.approval_expired")
      -            return "No pending command to approve."
      +            return t("gateway.approve.no_pending")
       
               # Parse args: support "all", "all session", "all always", "session", "always"
               args = event.get_command_args().strip().lower().split()
      @@ -11681,26 +11599,23 @@ async def _handle_approve_command(self, event: MessageEvent) -> Optional[str]:
       
               if any(a in ("always", "permanent", "permanently") for a in remaining):
                   choice = "always"
      -            scope_msg = " (pattern approved permanently)"
               elif any(a in ("session", "ses") for a in remaining):
                   choice = "session"
      -            scope_msg = " (pattern approved for this session)"
               else:
                   choice = "once"
      -            scope_msg = ""
       
               count = resolve_gateway_approval(session_key, choice, resolve_all=resolve_all)
               if not count:
      -            return "No pending command to approve."
      +            return t("gateway.approve.no_pending")
       
               # Resume typing indicator — agent is about to continue processing.
               _adapter = self.adapters.get(source.platform)
               if _adapter:
                   _adapter.resume_typing_for_chat(source.chat_id)
       
      -        count_msg = f" ({count} commands)" if count > 1 else ""
      -        logger.info("User approved %d dangerous command(s) via /approve%s", count, scope_msg)
      -        return f"✅ Command{'s' if count > 1 else ''} approved{scope_msg}{count_msg}. The agent is resuming..."
      +        logger.info("User approved %d dangerous command(s) via /approve (%s)", count, choice)
      +        plural = "plural" if count > 1 else "singular"
      +        return t(f"gateway.approve.{choice}_{plural}", count=count)
       
           async def _handle_deny_command(self, event: MessageEvent) -> str:
               """Handle /deny command — reject pending dangerous command(s).
      @@ -11720,24 +11635,25 @@ async def _handle_deny_command(self, event: MessageEvent) -> str:
               if not has_blocking_approval(session_key):
                   if session_key in self._pending_approvals:
                       self._pending_approvals.pop(session_key)
      -                return "❌ Command denied (approval was stale)."
      -            return "No pending command to deny."
      +                return t("gateway.deny.stale")
      +            return t("gateway.deny.no_pending")
       
               args = event.get_command_args().strip().lower()
               resolve_all = "all" in args
       
               count = resolve_gateway_approval(session_key, "deny", resolve_all=resolve_all)
               if not count:
      -            return "No pending command to deny."
      +            return t("gateway.deny.no_pending")
       
               # Resume typing indicator — agent continues (with BLOCKED result).
               _adapter = self.adapters.get(source.platform)
               if _adapter:
                   _adapter.resume_typing_for_chat(source.chat_id)
       
      -        count_msg = f" ({count} commands)" if count > 1 else ""
               logger.info("User denied %d dangerous command(s) via /deny", count)
      -        return f"❌ Command{'s' if count > 1 else ''} denied{count_msg}."
      +        if count > 1:
      +            return t("gateway.deny.denied_plural", count=count)
      +        return t("gateway.deny.denied_singular")
       
           # Platforms where /update is allowed.  ACP, API server, and webhooks are
           # programmatic interfaces that should not trigger system updates.
      @@ -11774,20 +11690,20 @@ def _collect_and_upload():
                   try:
                       urls["Report"] = upload_to_pastebin(report)
                   except Exception as exc:
      -                return f"✗ Failed to upload debug report: {exc}"
      +                return t("gateway.debug.upload_failed", error=exc)
       
                   # Schedule auto-deletion after 6 hours
                   _schedule_auto_delete(list(urls.values()))
       
      -            lines = [_GATEWAY_PRIVACY_NOTICE, "", "**Debug report uploaded:**", ""]
      +            lines = [_GATEWAY_PRIVACY_NOTICE, "", t("gateway.debug.header"), ""]
                   label_width = max(len(k) for k in urls)
                   for label, url in urls.items():
                       lines.append(f"`{label:<{label_width}}`  {url}")
       
                   lines.append("")
      -            lines.append("⏱ Pastes will auto-delete in 6 hours.")
      -            lines.append("For full log uploads, use `hermes debug share` from the CLI.")
      -            lines.append("Share these links with the Hermes team for support.")
      +            lines.append(t("gateway.debug.auto_delete"))
      +            lines.append(t("gateway.debug.full_logs_hint"))
      +            lines.append(t("gateway.debug.share_hint"))
                   return "\n".join(lines)
       
               return await loop.run_in_executor(None, _collect_and_upload)
      @@ -11815,9 +11731,9 @@ async def _handle_update_command(self, event: MessageEvent) -> str:
                       from gateway.platform_registry import platform_registry
                       entry = platform_registry.get(platform.value)
                       if not entry or not entry.allow_update_command:
      -                    return "✗ /update is only available from messaging platforms. Run `hermes update` from the terminal."
      +                    return t("gateway.update.platform_not_messaging")
                   except Exception:
      -                return "✗ /update is only available from messaging platforms. Run `hermes update` from the terminal."
      +                return t("gateway.update.platform_not_messaging")
       
               if is_managed():
                   return f"✗ {format_managed_message('update Hermes Agent')}"
      @@ -11826,16 +11742,11 @@ async def _handle_update_command(self, event: MessageEvent) -> str:
               git_dir = project_root / '.git'
       
               if not git_dir.exists():
      -            return "✗ Not a git repository — cannot update."
      +            return t("gateway.update.not_git_repo")
       
               hermes_cmd = _resolve_hermes_bin()
               if not hermes_cmd:
      -            return (
      -                "✗ Could not locate the `hermes` command. "
      -                "Hermes is running, but the update command could not find the "
      -                "executable on PATH or via the current Python interpreter. "
      -                "Try running `hermes update` manually in your terminal."
      -            )
      +            return t("gateway.update.hermes_cmd_not_found")
       
               pending_path = _hermes_home / ".update_pending.json"
               output_path = _hermes_home / ".update_output.txt"
      @@ -11938,10 +11849,10 @@ async def _handle_update_command(self, event: MessageEvent) -> str:
               except Exception as e:
                   pending_path.unlink(missing_ok=True)
                   exit_code_path.unlink(missing_ok=True)
      -            return f"✗ Failed to start update: {e}"
      +            return t("gateway.update.start_failed", error=e)
       
               self._schedule_update_notification_watch()
      -        return "⚕ Starting Hermes update… I'll stream progress here."
      +        return t("gateway.update.starting")
       
           def _schedule_update_notification_watch(self) -> None:
               """Ensure a background task is watching for update completion."""
    • locales/af.yaml
      @@ -0,0 +1,350 @@
      +# Hermes statiese boodskap-katalogus -- Afrikaans
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  GEVAARLIKE OPDRAG: {description}"
      +  choose_long:     "      [o]eenmalig  |  [s]sessie  |  [a]altyd  |  [d]weier"
      +  choose_short:    "      [o]eenmalig  |  [s]sessie  |  [d]weier"
      +  prompt_long:     "      Keuse [o/s/a/D]: "
      +  prompt_short:    "      Keuse [o/s/D]: "
      +  timeout:         "      ⏱ Tyd verstreke - opdrag word geweier"
      +  allowed_once:    "      ✓ Eenmalig toegelaat"
      +  allowed_session: "      ✓ Vir hierdie sessie toegelaat"
      +  allowed_always:  "      ✓ By permanente toelaatlys gevoeg"
      +  denied:          "      ✗ Geweier"
      +  cancelled:       "      ✗ Gekanselleer"
      +  blocklist_message: "Hierdie opdrag is op die onvoorwaardelike blokkeerlys en kan nie goedgekeur word nie."
      +
      +gateway:
      +  approval_expired: "⚠️ Goedkeuring het verval (die agent wag nie meer nie). Vra die agent om weer te probeer."
      +  draining:         "⏳ Wag vir {count} aktiewe agent(e) voor herbegin..."
      +  goal_cleared:     "✓ Doelwit verwyder."
      +  no_active_goal:   "Geen aktiewe doelwit nie."
      +  config_read_failed: "⚠️ Kon nie config.yaml lees nie: {error}"
      +  config_save_failed: "⚠️ Kon nie konfigurasie stoor nie: {error}"
      +
      +  model:
      +    error_prefix:           "Fout: {error}"
      +    switched:               "Model verander na `{model}`"
      +    provider_label:         "Verskaffer: {provider}"
      +    context_label:          "Konteks: {tokens} tokens"
      +    max_output_label:       "Maks. uitvoer: {tokens} tokens"
      +    cost_label:             "Koste: {cost}"
      +    capabilities_label:     "Vermoëns: {capabilities}"
      +    prompt_caching_enabled: "Prompt-kasing: geaktiveer"
      +    warning_prefix:         "Waarskuwing: {warning}"
      +    saved_global:           "Gestoor in config.yaml (`--global`)"
      +    session_only_hint:      "_(slegs sessie — voeg `--global` by om permanent te stoor)_"
      +    current_label:          "Huidig: `{model}` op {provider}"
      +    current_tag:            " (huidig)"
      +    more_models_suffix:     " (+{count} meer)"
      +    usage_switch_model:     "`/model <name>` — verander model"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — verander verskaffer"
      +    usage_persist:          "`/model <name> --global` — stoor permanent"
      +
      +  agents:
      +    header:                "🤖 **Aktiewe Agente & Take**"
      +    active_agents:         "**Aktiewe agente:** {count}"
      +    this_chat:             " · hierdie geselsie"
      +    more:                  "... en nog {count}"
      +    running_processes:     "**Lopende agtergrondprosesse:** {count}"
      +    async_jobs:            "**Asinchrone werke van die gateway:** {count}"
      +    none:                  "Geen aktiewe agente of lopende take nie."
      +    state_starting:        "begin"
      +    state_running:         "loop"
      +
      +  approve:
      +    no_pending:            "Geen hangende opdrag om goed te keur nie."
      +    once_singular:         "✅ Opdrag goedgekeur. Die agent gaan voort..."
      +    once_plural:           "✅ Opdragte goedgekeur ({count} opdragte). Die agent gaan voort..."
      +    session_singular:      "✅ Opdrag goedgekeur (patroon goedgekeur vir hierdie sessie). Die agent gaan voort..."
      +    session_plural:        "✅ Opdragte goedgekeur (patroon goedgekeur vir hierdie sessie) ({count} opdragte). Die agent gaan voort..."
      +    always_singular:       "✅ Opdrag goedgekeur (patroon permanent goedgekeur). Die agent gaan voort..."
      +    always_plural:         "✅ Opdragte goedgekeur (patroon permanent goedgekeur) ({count} opdragte). Die agent gaan voort..."
      +
      +  background:
      +    usage:                 "Gebruik: /background <prompt>\nVoorbeeld: /background Som vandag se top HN-stories op\n\nVoer die prompt in 'n aparte sessie uit. Jy kan aanhou gesels — die resultaat verskyn hier wanneer dit klaar is."
      +    started:               "🔄 Agtergrondtaak begin: \"{preview}\"\nTaak-ID: {task_id}\nJy kan aanhou gesels — resultate verskyn hier wanneer dit klaar is."
      +
      +  branch:
      +    db_unavailable:        "Sessie-databasis is nie beskikbaar nie."
      +    no_conversation:       "Geen gesprek om te vertak nie — stuur eers 'n boodskap."
      +    create_failed:         "Kon nie tak skep nie: {error}"
      +    switch_failed:         "Tak is geskep, maar oorskakeling het misluk."
      +    branched_one:          "⑂ Vertak na **{title}** ({count} boodskap gekopieer)\nOorspronklik: `{parent}`\nTak: `{new}`\nGebruik `/resume` om terug te gaan na die oorspronklike."
      +    branched_many:         "⑂ Vertak na **{title}** ({count} boodskappe gekopieer)\nOorspronklik: `{parent}`\nTak: `{new}`\nGebruik `/resume` om terug te gaan na die oorspronklike."
      +
      +  commands:
      +    usage:                 "Gebruik: `/commands [page]`"
      +    skill_header:          "⚡ **Vaardigheidsopdragte**:"
      +    default_desc:          "Vaardigheidsopdrag"
      +    none:                  "Geen opdragte beskikbaar nie."
      +    header:                "📚 **Opdragte** ({total} altesaam, bladsy {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← vorige"
      +    nav_next:              "volgende → `/commands {page}`"
      +    out_of_range:          "_(Versoekte bladsy {requested} was buite reikwydte; bladsy {page} word vertoon.)_"
      +
      +  compress:
      +    not_enough:            "Nie genoeg gesprek om saam te pers nie (ten minste 4 boodskappe nodig)."
      +    no_provider:           "Geen verskaffer opgestel nie -- kan nie saampers nie."
      +    nothing_to_do:         "Niks om saam te pers nie (die transkripsie is steeds heeltemal beskermde konteks)."
      +    focus_line:            "Fokus: \"{topic}\""
      +    summary_failed:        "⚠️ Opsomming kon nie gegenereer word nie ({error}). {count} historiese boodskap(pe) is verwyder en met 'n plekhouer vervang; vroeëre konteks kan nie meer herstel word nie. Oorweeg om jou auxiliary.compression-modelopstelling na te gaan."
      +    aux_failed:            "ℹ️ Opgestelde saamperseringsmodel `{model}` het misluk ({error}). Herstel met jou hoofmodel — konteks is intakt — maar jy mag dalk `auxiliary.compression.model` in config.yaml wil nagaan."
      +    failed:                "Saampersing het misluk: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Kon nie ontfoutverslag oplaai nie: {error}"
      +    header:                "**Ontfoutverslag opgelaai:**"
      +    auto_delete:           "⏱ Plakke sal outomaties oor 6 uur uitgevee word."
      +    full_logs_hint:        "Vir volledige loglae, gebruik `hermes debug share` vanaf die CLI."
      +    share_hint:            "Deel hierdie skakels met die Hermes-span vir ondersteuning."
      +
      +  deny:
      +    stale:                 "❌ Opdrag geweier (goedkeuring was verouderd)."
      +    no_pending:            "Geen hangende opdrag om te weier nie."
      +    denied_singular:       "❌ Opdrag geweier."
      +    denied_plural:         "❌ Opdragte geweier ({count} opdragte)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast is slegs beskikbaar vir OpenAI-modelle wat Priority Processing ondersteun."
      +    status:                "⚡ Priority Processing\n\nHuidige modus: `{mode}`\n\n_Gebruik:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Onbekende argument: `{arg}`\n\n**Geldige opsies:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (gestoor in konfigurasie)\n_(neem effek by die volgende boodskap)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (slegs hierdie sessie)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Looptyd-voetstuk: **{state}**\nVelde: `{fields}`\nPlatform: `{platform}`"
      +    usage:                 "Gebruik: `/footer [on|off|status]`"
      +    saved:                 "📎 Looptyd-voetstuk: **{state}**{example}\n_(globaal gestoor — neem effek by die volgende boodskap)_"
      +    example_line:          "\nVoorbeeld: `{preview}`"
      +    state_on:              "AAN"
      +    state_off:             "AF"
      +
      +  goal:
      +    unavailable:           "Doelwitte is nie beskikbaar in hierdie sessie nie."
      +    no_goal_set:           "Geen doelwit gestel nie."
      +    paused:                "⏸ Doelwit gepouse: {goal}"
      +    no_resume:             "Geen doelwit om voort te sit nie."
      +    resumed:               "▶ Doelwit hervat: {goal}\nStuur enige boodskap om voort te gaan, of wag — ek sal die volgende stap met die volgende beurt neem."
      +    invalid:               "Ongeldige doelwit: {error}"
      +    set:                   "⊙ Doelwit gestel ({budget}-beurt-begroting): {goal}\nEk sal aanhou werk totdat die doelwit klaar is, jy dit pouseer/verwyder, of die begroting opgebruik is.\nBeheer: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes-opdragte**\n"
      +    skill_header:          "\n⚡ **Vaardigheidsopdragte** ({count} aktief):"
      +    more_use_commands:     "\n... en nog {count}. Gebruik `/commands` vir die volledige bladsy-lys."
      +
      +  insights:
      +    invalid_days:          "Ongeldige --days waarde: {value}"
      +    error:                 "Fout met genereer van insigte: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban-fout: {error}"
      +    subscribed_suffix:     "(ingeteken — jy sal in kennis gestel word wanneer {task_id} voltooi of vasval)"
      +    truncated_suffix:      "… (afgekap; gebruik `hermes kanban …` in jou terminale vir volle uitvoer)"
      +    no_output:             "(geen uitvoer)"
      +
      +  personality:
      +    none_configured:       "Geen persoonlikhede opgestel in `{path}/config.yaml` nie"
      +    header:                "🎭 **Beskikbare Persoonlikhede**\n"
      +    none_option:           "• `none` — (geen persoonlikheidslaag)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nGebruik: `/personality <name>`"
      +    save_failed:           "⚠️ Kon nie persoonlikheidsverandering stoor nie: {error}"
      +    cleared:               "🎭 Persoonlikheid verwyder — basis-agentgedrag word gebruik.\n_(neem effek by die volgende boodskap)_"
      +    set_to:                "🎭 Persoonlikheid gestel op **{name}**\n_(neem effek by die volgende boodskap)_"
      +    unknown:               "Onbekende persoonlikheid: `{name}`\n\nBeskikbaar: {available}"
      +
      +  profile:
      +    header:                "👤 **Profiel:** `{profile}`"
      +    home:                  "📂 **Tuiste:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (verstek)"
      +    level_disabled:            "none (gedeaktiveer)"
      +    scope_session:             "sessie-oorskryf"
      +    scope_global:              "globale konfigurasie"
      +    status:                    "🧠 **Redenering-instellings**\n\n**Inspanning:** `{level}`\n**Bereik:** {scope}\n**Vertoon:** {display}\n\n_Gebruik:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "aan ✓"
      +    display_off:               "af"
      +    display_set_on:            "🧠 ✓ Redenering-vertoon: **AAN**\nDie model se denke sal voor elke antwoord op **{platform}** vertoon word."
      +    display_set_off:           "🧠 ✓ Redenering-vertoon: **AF** vir **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` word nie ondersteun nie. Gebruik `/reasoning <level> --global` om die globale verstek te verander."
      +    reset_done:                "🧠 ✓ Sessie-redenering-oorskryf verwyder; val terug op globale konfigurasie."
      +    unknown_arg:               "⚠️ Onbekende argument: `{arg}`\n\n**Geldige vlakke:** none, minimal, low, medium, high, xhigh\n**Vertoon:** show, hide\n**Permanent:** voeg `--global` by om verby hierdie sessie te stoor"
      +    set_global:                "🧠 ✓ Redenering-inspanning gestel op `{effort}` (gestoor in konfigurasie)\n_(neem effek by die volgende boodskap)_"
      +    set_global_save_failed:    "🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — konfigurasie-stoor het misluk)\n_(neem effek by die volgende boodskap)_"
      +    set_session:               "🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — voeg `--global` by om permanent te stoor)\n_(neem effek by die volgende boodskap)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp gekanselleer. MCP-gereedskap onveranderd."
      +    always_followup:       "ℹ️ Toekomstige `/reload-mcp`-oproepe sal sonder bevestiging loop. Heraktiveer via `approvals.mcp_reload_confirm: true` in config.yaml."
      +    confirm_prompt:        "⚠️ **Bevestig /reload-mcp**\n\nOm MCP-bedieners te herlaai, herbou die gereedskapsstel vir hierdie sessie en **maak die verskaffer se prompt-kasie ongeldig** — die volgende boodskap sal alle invoertokens herstuur. Op modelle met lang konteks of hoë redenering kan dit duur wees.\n\nKies:\n• **Eenmaal Goedkeur** — herlaai nou\n• **Altyd Goedkeur** — herlaai nou en stop hierdie prompt permanent\n• **Kanselleer** — laat MCP-gereedskap onveranderd\n\n_Teks-alternatief: antwoord `/approve`, `/always`, of `/cancel`._"
      +    header:                "🔄 **MCP-bedieners herlaai**\n"
      +    reconnected:           "♻️ Herverbind: {names}"
      +    added:                 "➕ Bygevoeg: {names}"
      +    removed:               "➖ Verwyder: {names}"
      +    none_connected:        "Geen MCP-bedieners verbind nie."
      +    tools_available:       "\n🔧 {tools} gereedskap beskikbaar van {servers} bediener(s)"
      +    failed:                "❌ MCP-herlaai het misluk: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Vaardighede herlaai**\n"
      +    no_new:                "Geen nuwe vaardighede opgespoor nie."
      +    total:                 "\n📚 {count} vaardigheid(e) beskikbaar"
      +    added_header:          "➕ **Bygevoegde Vaardighede:**"
      +    removed_header:        "➖ **Verwyderde Vaardighede:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Vaardigheids-herlaai het misluk: {error}"
      +
      +  reset:
      +    header_default:        "✨ Sessie herstel! Begin van voor."
      +    header_new:            "✨ Nuwe sessie begin!"
      +    header_titled:         "✨ Nuwe sessie begin: {title}"
      +    title_rejected:        "\n⚠️ Titel verwerp: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — sessie sonder titel begin."
      +    title_empty_untitled:  "\n⚠️ Titel is leeg na opruiming — sessie sonder titel begin."
      +    tip:                   "\n✦ Wenk: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Gateway-herbegin reeds aan die gang..."
      +    restarting:            "♻ Herbegin van gateway. As jy nie binne 60 sekondes in kennis gestel word nie, herbegin vanaf die konsole met `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Sessie-databasis is nie beskikbaar nie."
      +    no_named_sessions:     "Geen benoemde sessies gevind nie.\nGebruik `/title My Sessie` om jou huidige sessie 'n naam te gee, en dan `/resume My Sessie` om later daarheen terug te keer."
      +    list_header:           "📋 **Benoemde Sessies**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nGebruik: `/resume <session name>`"
      +    list_failed:           "Kon nie sessies lys nie: {error}"
      +    not_found:             "Geen sessie gevind wat by '**{name}**' pas nie.\nGebruik `/resume` sonder argumente om beskikbare sessies te sien."
      +    already_on:            "📌 Reeds op sessie **{name}**."
      +    switch_failed:         "Kon nie sessie verander nie."
      +    resumed_one:           "↻ Sessie **{title}** hervat ({count} boodskap). Gesprek herstel."
      +    resumed_many:          "↻ Sessie **{title}** hervat ({count} boodskappe). Gesprek herstel."
      +    resumed_no_count:      "↻ Sessie **{title}** hervat. Gesprek herstel."
      +
      +  retry:
      +    no_previous:           "Geen vorige boodskap om te herhaal nie."
      +
      +  rollback:
      +    not_enabled:           "Kontrolepunte is nie geaktiveer nie.\nAktiveer in config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Geen kontrolepunte vir {cwd} gevind nie"
      +    invalid_number:        "Ongeldige kontrolepunt-nommer. Gebruik 1-{max}."
      +    restored:              "✅ Herstel na kontrolepunt {hash}: {reason}\n'n Voor-terugrol-momentopname is outomaties gestoor."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Kon nie tuiste-kanaal stoor nie: {error}"
      +    success:               "✅ Tuiste-kanaal gestel op **{name}** (ID: {chat_id}).\nKron-take en kruisplatform-boodskappe sal hier afgelewer word."
      +
      +  status:
      +    header:                "📊 **Hermes Gateway Status**"
      +    session_id:            "**Sessie-ID:** `{session_id}`"
      +    title:                 "**Titel:** {title}"
      +    created:               "**Geskep:** {timestamp}"
      +    last_activity:         "**Laaste aktiwiteit:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agent loop:** {state}"
      +    state_yes:             "Ja ⚡"
      +    state_no:              "Nee"
      +    queued:                "**Opgehoopte opvolge:** {count}"
      +    platforms:             "**Verbinde Platforms:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Gestop. Die agent het nog nie begin nie — jy kan met hierdie sessie voortgaan."
      +    stopped:               "⚡ Gestop. Jy kan met hierdie sessie voortgaan."
      +    no_active:             "Geen aktiewe taak om te stop nie."
      +
      +  title:
      +    db_unavailable:        "Sessie-databasis is nie beskikbaar nie."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Titel is leeg na opruiming. Gebruik asseblief drukbare karakters."
      +    set_to:                "✏️ Sessie-titel gestel: **{title}**"
      +    not_found:             "Sessie nie in databasis gevind nie."
      +    current_with_title:    "📌 Sessie: `{session_id}`\nTitel: **{title}**"
      +    current_no_title:      "📌 Sessie: `{session_id}`\nGeen titel gestel nie. Gebruik: `/title My Sessie Naam`"
      +
      +  topic:
      +    not_telegram_dm:         "Die /topic-opdrag is slegs beskikbaar in Telegram-privaatgesprekke."
      +    no_session_db:           "Sessie-databasis is nie beskikbaar nie."
      +    unauthorized:            "Jy het nie toestemming om /topic op hierdie bot te gebruik nie."
      +    restore_needs_topic:     "Om 'n sessie te herstel, skep of open eers 'n Telegram-onderwerp en stuur dan /topic <session-id> binne daardie onderwerp. Om 'n nuwe onderwerp te skep, open All Messages en stuur enige boodskap daar."
      +    topics_disabled:         "Telegram-onderwerpe is nog nie vir hierdie bot geaktiveer nie.\n\nHoe om dit te aktiveer:\n1. Open @BotFather.\n2. Kies jou bot.\n3. Open Bot Settings → Threads Settings.\n4. Skakel Threaded Mode aan en maak seker gebruikers mag nuwe drade skep.\n\nStuur dan weer /topic."
      +    topics_user_disallowed:  "Telegram-onderwerpe is geaktiveer, maar gebruikers mag nie onderwerpe skep nie.\n\nOpen @BotFather → kies jou bot → Bot Settings → Threads Settings, en skakel dan 'Disallow users to create new threads' af.\n\nStuur dan weer /topic."
      +    enable_failed:           "Kon nie Telegram-onderwerpmodus aktiveer nie: {error}"
      +    bound_status:            "Hierdie onderwerp is gekoppel aan:\nSessie: {label}\nID: {session_id}\n\nGebruik /new om hierdie onderwerp met 'n vars sessie te vervang.\nVir parallelle werk, open All Messages en stuur 'n boodskap daar om 'n ander onderwerp te skep."
      +    thread_ready:            "Telegram multi-sessie-onderwerpe is geaktiveer.\n\nHierdie onderwerp sal as 'n onafhanklike Hermes-sessie gebruik word. Gebruik /new om hierdie onderwerp se huidige sessie te vervang. Vir parallelle werk, open All Messages en stuur 'n boodskap daar om 'n ander onderwerp te skep."
      +    untitled_session:        "Sessie sonder titel"
      +
      +  undo:
      +    nothing:               "Niks om ongedaan te maak nie."
      +    removed:               "↩️ {count} boodskap(pe) ongedaan gemaak.\nVerwyder: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update is slegs beskikbaar vanaf boodskapplatforms. Voer `hermes update` vanaf die terminale uit."
      +    not_git_repo:            "✗ Nie 'n git-bewaarplek nie — kan nie opdateer nie."
      +    hermes_cmd_not_found:    "✗ Kon nie die `hermes`-opdrag vind nie. Hermes loop, maar die opdateeropdrag kon nie die uitvoerbare lêer op PATH of via die huidige Python-vertolker vind nie. Probeer `hermes update` met die hand in jou terminale uitvoer."
      +    start_failed:            "✗ Kon nie opdatering begin nie: {error}"
      +    starting:                "⚕ Begin Hermes-opdatering… Ek sal vordering hier stroom."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Tariefperke:** {state}"
      +    header_session:           "📊 **Sessie-tokengebruik**"
      +    label_model:              "Model: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] {count}"
      +    label_cache_read:         "Kasie-leestokens: [redacted:secret-assignment]"
      +    label_cache_write:        "Kasie-skryftokens: [redacted:secret-assignment]"
      +    label_output_tokens:      "[redacted:secret-assignment] {count}"
      +    label_total:              "Totaal: {count}"
      +    label_api_calls:          "API-oproepe: {count}"
      +    label_cost:               "Koste: {prefix}${amount}"
      +    label_cost_included:      "Koste: ingesluit"
      +    label_context:            "Konteks: {used} / {total} ({pct}%)"
      +    label_compressions:       "Saamperserings: {count}"
      +    header_session_info:      "📊 **Sessie-inligting**"
      +    label_messages:           "Boodskappe: {count}"
      +    label_estimated_context:  "Geskatte konteks: ~{count} tokens"
      +    detailed_after_first:     "_(Gedetailleerde gebruik beskikbaar na die eerste agent-antwoord)_"
      +    no_data:                  "Geen gebruiksdata beskikbaar vir hierdie sessie nie."
      +
      +  verbose:
      +    not_enabled:           "Die `/verbose`-opdrag is nie vir boodskapplatforms geaktiveer nie.\n\nAktiveer dit in `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Gereedskap-vordering: **AF** — geen gereedskap-aktiwiteit word vertoon nie."
      +    mode_new:              "⚙️ Gereedskap-vordering: **NUUT** — vertoon wanneer gereedskap verander (voorskoulengte: `display.tool_preview_length`, verstek 40)."
      +    mode_all:              "⚙️ Gereedskap-vordering: **ALMAL** — elke gereedskaps-oproep vertoon (voorskoulengte: `display.tool_preview_length`, verstek 40)."
      +    mode_verbose:          "⚙️ Gereedskap-vordering: **OMSLAGTIG** — elke gereedskaps-oproep met volle argumente."
      +    saved_suffix:          "_(gestoor vir **{platform}** — neem effek by die volgende boodskap)_"
      +    save_failed:           "_(kon nie in konfigurasie stoor nie: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Stemmodus geaktiveer.\nEk sal met stem antwoord wanneer jy stemboodskappe stuur.\nGebruik /voice tts om stemantwoorde vir alle boodskappe te kry."
      +    disabled_text:         "Stemmodus gedeaktiveer. Slegs teks-antwoorde."
      +    tts_enabled:           "Outo-TTS geaktiveer.\nAlle antwoorde sal 'n stemboodskap insluit."
      +    status_mode:           "Stemmodus: {label}"
      +    status_channel:        "Stemkanaal: #{channel}"
      +    status_participants:   "Deelnemers: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (praat)"
      +    enabled_short:         "Stemmodus geaktiveer."
      +    disabled_short:        "Stemmodus gedeaktiveer."
      +    label_off:             "Af (slegs teks)"
      +    label_voice_only:      "Aan (stemantwoord op stemboodskappe)"
      +    label_all:             "TTS (stemantwoord op alle boodskappe)"
      +
      +  yolo:
      +    disabled:                   "⚠️ YOLO-modus **AF** vir hierdie sessie — gevaarlike opdragte sal goedkeuring vereis."
      +    enabled:                    "⚡ YOLO-modus **AAN** vir hierdie sessie — alle opdragte word outomaties goedgekeur. Gebruik versigtig."
      +
      +  shared:
      +    session_db_unavailable:      "Sessie-databasis is nie beskikbaar nie."
      +    session_db_unavailable_prefix: "Sessie-databasis is nie beskikbaar"
      +    session_not_found:           "Sessie nie in databasis gevind nie."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/de.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "Kein aktives Ziel."
         config_read_failed: "⚠️ config.yaml konnte nicht gelesen werden: {error}"
         config_save_failed: "⚠️ Konfiguration konnte nicht gespeichert werden: {error}"
      +
      +  model:
      +    error_prefix:           "Fehler: {error}"
      +    switched:               "Modell gewechselt zu `{model}`"
      +    provider_label:         "Anbieter: {provider}"
      +    context_label:          "Kontext: {tokens} Tokens"
      +    max_output_label:       "Max. Ausgabe: {tokens} Tokens"
      +    cost_label:             "Kosten: {cost}"
      +    capabilities_label:     "Fähigkeiten: {capabilities}"
      +    prompt_caching_enabled: "Prompt-Caching: aktiviert"
      +    warning_prefix:         "Warnung: {warning}"
      +    saved_global:           "In config.yaml gespeichert (`--global`)"
      +    session_only_hint:      "_(nur für diese Sitzung — `--global` ergänzen, um zu speichern)_"
      +    current_label:          "Aktuell: `{model}` bei {provider}"
      +    current_tag:            " (aktuell)"
      +    more_models_suffix:     " (+{count} weitere)"
      +    usage_switch_model:     "`/model <name>` — Modell wechseln"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — Anbieter wechseln"
      +    usage_persist:          "`/model <name> --global` — dauerhaft speichern"
      +
      +  agents:
      +    header:                "🤖 **Aktive Agenten & Aufgaben**"
      +    active_agents:         "**Aktive Agenten:** {count}"
      +    this_chat:             " · dieser Chat"
      +    more:                  "... und {count} weitere"
      +    running_processes:     "**Laufende Hintergrundprozesse:** {count}"
      +    async_jobs:            "**Gateway-Async-Jobs:** {count}"
      +    none:                  "Keine aktiven Agenten oder laufenden Aufgaben."
      +    state_starting:        "startet"
      +    state_running:         "läuft"
      +
      +  approve:
      +    no_pending:            "Kein ausstehender Befehl zum Genehmigen."
      +    once_singular:         "✅ Befehl genehmigt. Der Agent wird fortgesetzt..."
      +    once_plural:           "✅ Befehle genehmigt ({count} Befehle). Der Agent wird fortgesetzt..."
      +    session_singular:      "✅ Befehl genehmigt (Muster für diese Sitzung genehmigt). Der Agent wird fortgesetzt..."
      +    session_plural:        "✅ Befehle genehmigt (Muster für diese Sitzung genehmigt) ({count} Befehle). Der Agent wird fortgesetzt..."
      +    always_singular:       "✅ Befehl genehmigt (Muster dauerhaft genehmigt). Der Agent wird fortgesetzt..."
      +    always_plural:         "✅ Befehle genehmigt (Muster dauerhaft genehmigt) ({count} Befehle). Der Agent wird fortgesetzt..."
      +
      +  background:
      +    usage:                 "Verwendung: /background <prompt>\nBeispiel: /background Fasse die Top-HN-Storys von heute zusammen\n\nFührt den Prompt in einer separaten Sitzung aus. Sie können weiter chatten — das Ergebnis erscheint hier, wenn es fertig ist."
      +    started:               "🔄 Hintergrund-Aufgabe gestartet: \"{preview}\"\nAufgaben-ID: {task_id}\nSie können weiter chatten — die Ergebnisse erscheinen hier, wenn sie fertig sind."
      +
      +  branch:
      +    db_unavailable:        "Sitzungsdatenbank nicht verfügbar."
      +    no_conversation:       "Keine Konversation zum Verzweigen — senden Sie zuerst eine Nachricht."
      +    create_failed:         "Verzweigung fehlgeschlagen: {error}"
      +    switch_failed:         "Verzweigung erstellt, aber Wechsel fehlgeschlagen."
      +    branched_one:          "⑂ Verzweigt zu **{title}** ({count} Nachricht kopiert)\nOriginal: `{parent}`\nZweig: `{new}`\nVerwenden Sie `/resume`, um zum Original zurückzukehren."
      +    branched_many:         "⑂ Verzweigt zu **{title}** ({count} Nachrichten kopiert)\nOriginal: `{parent}`\nZweig: `{new}`\nVerwenden Sie `/resume`, um zum Original zurückzukehren."
      +
      +  commands:
      +    usage:                 "Verwendung: `/commands [page]`"
      +    skill_header:          "⚡ **Skill-Befehle**:"
      +    default_desc:          "Skill-Befehl"
      +    none:                  "Keine Befehle verfügbar."
      +    header:                "📚 **Befehle** ({total} insgesamt, Seite {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← zurück"
      +    nav_next:              "weiter → `/commands {page}`"
      +    out_of_range:          "_(Angeforderte Seite {requested} liegt außerhalb des Bereichs, Seite {page} wird angezeigt.)_"
      +
      +  compress:
      +    not_enough:            "Nicht genug Konversation zum Komprimieren (mindestens 4 Nachrichten erforderlich)."
      +    no_provider:           "Kein Anbieter konfiguriert — Komprimierung nicht möglich."
      +    nothing_to_do:         "Noch nichts zu komprimieren (das Transkript ist weiterhin vollständig geschützter Kontext)."
      +    focus_line:            "Fokus: \"{topic}\""
      +    summary_failed:        "⚠️ Zusammenfassungsgenerierung fehlgeschlagen ({error}). {count} historische Nachricht(en) wurden entfernt und durch einen Platzhalter ersetzt; früherer Kontext ist nicht mehr wiederherstellbar. Überprüfen Sie die Konfiguration des auxiliary.compression-Modells."
      +    aux_failed:            "ℹ️ Das konfigurierte Komprimierungsmodell `{model}` ist fehlgeschlagen ({error}). Wiederherstellung mit Ihrem Hauptmodell — Kontext ist intakt — Sie sollten jedoch `auxiliary.compression.model` in config.yaml überprüfen."
      +    failed:                "Komprimierung fehlgeschlagen: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Debug-Bericht konnte nicht hochgeladen werden: {error}"
      +    header:                "**Debug-Bericht hochgeladen:**"
      +    auto_delete:           "⏱ Pastes werden in 6 Stunden automatisch gelöscht."
      +    full_logs_hint:        "Für vollständige Log-Uploads verwenden Sie `hermes debug share` aus der CLI."
      +    share_hint:            "Teilen Sie diese Links mit dem Hermes-Team, um Unterstützung zu erhalten."
      +
      +  deny:
      +    stale:                 "❌ Befehl abgelehnt (Genehmigung war veraltet)."
      +    no_pending:            "Kein ausstehender Befehl zum Ablehnen."
      +    denied_singular:       "❌ Befehl abgelehnt."
      +    denied_plural:         "❌ Befehle abgelehnt ({count} Befehle)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast ist nur für OpenAI-Modelle mit Priority Processing verfügbar."
      +    status:                "⚡ Priority Processing\n\nAktueller Modus: `{mode}`\n\n_Verwendung:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Unbekanntes Argument: `{arg}`\n\n**Gültige Optionen:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (in Konfiguration gespeichert)\n_(wird ab nächster Nachricht wirksam)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (nur diese Sitzung)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Laufzeit-Fußzeile: **{state}**\nFelder: `{fields}`\nPlattform: `{platform}`"
      +    usage:                 "Verwendung: `/footer [on|off|status]`"
      +    saved:                 "📎 Laufzeit-Fußzeile: **{state}**{example}\n_(global gespeichert — wird ab nächster Nachricht wirksam)_"
      +    example_line:          "\nBeispiel: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Ziele sind in dieser Sitzung nicht verfügbar."
      +    no_goal_set:           "Kein Ziel gesetzt."
      +    paused:                "⏸ Ziel pausiert: {goal}"
      +    no_resume:             "Kein Ziel zum Fortsetzen."
      +    resumed:               "▶ Ziel fortgesetzt: {goal}\nSenden Sie eine Nachricht zum Fortfahren oder warten Sie — ich übernehme den nächsten Schritt im nächsten Zug."
      +    invalid:               "Ungültiges Ziel: {error}"
      +    set:                   "⊙ Ziel gesetzt ({budget}-Zug-Budget): {goal}\nIch arbeite weiter, bis das Ziel erreicht ist, Sie es pausieren/löschen oder das Budget aufgebraucht ist.\nSteuerung: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes-Befehle**\n"
      +    skill_header:          "\n⚡ **Skill-Befehle** ({count} aktiv):"
      +    more_use_commands:     "\n... und {count} weitere. Verwenden Sie `/commands` für die vollständige paginierte Liste."
      +
      +  insights:
      +    invalid_days:          "Ungültiger --days-Wert: {value}"
      +    error:                 "Fehler beim Erstellen der Auswertung: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ Kanban-Fehler: {error}"
      +    subscribed_suffix:     "(abonniert — Sie werden benachrichtigt, wenn {task_id} abgeschlossen oder blockiert wird)"
      +    truncated_suffix:      "… (gekürzt; verwenden Sie `hermes kanban …` im Terminal für die vollständige Ausgabe)"
      +    no_output:             "(keine Ausgabe)"
      +
      +  personality:
      +    none_configured:       "Keine Persönlichkeiten in `{path}/config.yaml` konfiguriert"
      +    header:                "🎭 **Verfügbare Persönlichkeiten**\n"
      +    none_option:           "• `none` — (kein Persönlichkeits-Overlay)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nVerwendung: `/personality <name>`"
      +    save_failed:           "⚠️ Speichern der Persönlichkeitsänderung fehlgeschlagen: {error}"
      +    cleared:               "🎭 Persönlichkeit gelöscht — Basisverhalten des Agenten wird verwendet.\n_(wird mit der nächsten Nachricht wirksam)_"
      +    set_to:                "🎭 Persönlichkeit auf **{name}** gesetzt\n_(wird mit der nächsten Nachricht wirksam)_"
      +    unknown:               "Unbekannte Persönlichkeit: `{name}`\n\nVerfügbar: {available}"
      +
      +  profile:
      +    header:                "👤 **Profil:** `{profile}`"
      +    home:                  "📂 **Stammverzeichnis:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (Standard)"
      +    level_disabled:            "none (deaktiviert)"
      +    scope_session:             "Sitzungs-Override"
      +    scope_global:              "Globale Konfiguration"
      +    status:                    "🧠 **Reasoning-Einstellungen**\n\n**Stärke:** `{level}`\n**Geltungsbereich:** {scope}\n**Anzeige:** {display}\n\n_Verwendung:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "an ✓"
      +    display_off:               "aus"
      +    display_set_on:            "🧠 ✓ Reasoning-Anzeige: **AN**\nDas Modelldenken wird vor jeder Antwort auf **{platform}** angezeigt."
      +    display_set_off:           "🧠 ✓ Reasoning-Anzeige: **AUS** für **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` wird nicht unterstützt. Verwenden Sie `/reasoning <level> --global`, um den globalen Standard zu ändern."
      +    reset_done:                "🧠 ✓ Sitzungs-Reasoning-Override gelöscht; Rückfall auf globale Konfiguration."
      +    unknown_arg:               "⚠️ Unbekanntes Argument: `{arg}`\n\n**Gültige Stärken:** none, minimal, low, medium, high, xhigh\n**Anzeige:** show, hide\n**Speichern:** `--global` hinzufügen, um über die Sitzung hinaus zu speichern"
      +    set_global:                "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (in Konfiguration gespeichert)\n_(wird mit der nächsten Nachricht wirksam)_"
      +    set_global_save_failed:    "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (nur Sitzung — Konfiguration konnte nicht gespeichert werden)\n_(wird mit der nächsten Nachricht wirksam)_"
      +    set_session:               "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (nur Sitzung — `--global` hinzufügen, um zu speichern)\n_(wird mit der nächsten Nachricht wirksam)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp abgebrochen. MCP-Tools unverändert."
      +    always_followup:       "ℹ️ Künftige `/reload-mcp`-Aufrufe laufen ohne Bestätigung. Wieder aktivieren über `approvals.mcp_reload_confirm: true` in `config.yaml`."
      +    confirm_prompt:        "⚠️ **/reload-mcp bestätigen**\n\nDas Neuladen der MCP-Server baut das Toolset für diese Sitzung neu auf und **invalidiert den Prompt-Cache des Anbieters** — die nächste Nachricht sendet die vollständigen Eingabetokens erneut. Bei langem Kontext oder Modellen mit hohem Reasoning-Aufwand kann das teuer sein.\n\nWählen Sie:\n• **Einmal genehmigen** — jetzt neu laden\n• **Immer genehmigen** — jetzt neu laden und diese Bestätigung dauerhaft unterdrücken\n• **Abbrechen** — MCP-Tools unverändert lassen\n\n_Text-Alternative: Antworten Sie mit `/approve`, `/always` oder `/cancel`._"
      +    header:                "🔄 **MCP-Server neu geladen**\n"
      +    reconnected:           "♻️ Wiederverbunden: {names}"
      +    added:                 "➕ Hinzugefügt: {names}"
      +    removed:               "➖ Entfernt: {names}"
      +    none_connected:        "Keine MCP-Server verbunden."
      +    tools_available:       "\n🔧 {tools} Tool(s) von {servers} Server(n) verfügbar"
      +    failed:                "❌ MCP-Neuladen fehlgeschlagen: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skills neu geladen**\n"
      +    no_new:                "Keine neuen Skills erkannt."
      +    total:                 "\n📚 {count} Skill(s) verfügbar"
      +    added_header:          "➕ **Hinzugefügte Skills:**"
      +    removed_header:        "➖ **Entfernte Skills:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Skill-Neuladen fehlgeschlagen: {error}"
      +
      +  reset:
      +    header_default:        "✨ Sitzung zurückgesetzt! Neuanfang."
      +    header_new:            "✨ Neue Sitzung gestartet!"
      +    header_titled:         "✨ Neue Sitzung gestartet: {title}"
      +    title_rejected:        "\n⚠️ Titel abgelehnt: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — Sitzung ohne Titel gestartet."
      +    title_empty_untitled:  "\n⚠️ Titel ist nach Bereinigung leer — Sitzung ohne Titel gestartet."
      +    tip:                   "\n✦ Tipp: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Gateway-Neustart läuft bereits..."
      +    restarting:            "♻ Gateway wird neu gestartet. Falls Sie nicht innerhalb von 60 Sekunden benachrichtigt werden, starten Sie über die Konsole mit `hermes gateway restart` neu."
      +
      +  resume:
      +    db_unavailable:        "Sitzungsdatenbank nicht verfügbar."
      +    no_named_sessions:     "Keine benannten Sitzungen gefunden.\nVerwenden Sie `/title Meine Sitzung`, um die aktuelle Sitzung zu benennen, dann `/resume Meine Sitzung`, um später dorthin zurückzukehren."
      +    list_header:           "📋 **Benannte Sitzungen**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nVerwendung: `/resume <Sitzungsname>`"
      +    list_failed:           "Sitzungen konnten nicht aufgelistet werden: {error}"
      +    not_found:             "Keine Sitzung passend zu '**{name}**' gefunden.\nVerwenden Sie `/resume` ohne Argumente, um verfügbare Sitzungen zu sehen."
      +    already_on:            "📌 Bereits in Sitzung **{name}**."
      +    switch_failed:         "Sitzungswechsel fehlgeschlagen."
      +    resumed_one:           "↻ Sitzung **{title}** fortgesetzt ({count} Nachricht). Konversation wiederhergestellt."
      +    resumed_many:          "↻ Sitzung **{title}** fortgesetzt ({count} Nachrichten). Konversation wiederhergestellt."
      +    resumed_no_count:      "↻ Sitzung **{title}** fortgesetzt. Konversation wiederhergestellt."
      +
      +  retry:
      +    no_previous:           "Keine vorherige Nachricht zum Wiederholen."
      +
      +  rollback:
      +    not_enabled:           "Checkpoints sind nicht aktiviert.\nIn config.yaml aktivieren:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Keine Checkpoints für {cwd} gefunden"
      +    invalid_number:        "Ungültige Checkpoint-Nummer. Verwenden Sie 1-{max}."
      +    restored:              "✅ Auf Checkpoint {hash} wiederhergestellt: {reason}\nEin Pre-Rollback-Snapshot wurde automatisch gespeichert."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Home-Kanal konnte nicht gespeichert werden: {error}"
      +    success:               "✅ Home-Kanal auf **{name}** (ID: {chat_id}) gesetzt.\nCron-Jobs und plattformübergreifende Nachrichten werden hierher geliefert."
      +
      +  status:
      +    header:                "📊 **Hermes-Gateway-Status**"
      +    session_id:            "**Sitzungs-ID:** `{session_id}`"
      +    title:                 "**Titel:** {title}"
      +    created:               "**Erstellt:** {timestamp}"
      +    last_activity:         "**Letzte Aktivität:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agent läuft:** {state}"
      +    state_yes:             "Ja ⚡"
      +    state_no:              "Nein"
      +    queued:                "**Wartende Folgenachrichten:** {count}"
      +    platforms:             "**Verbundene Plattformen:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Gestoppt. Der Agent hatte noch nicht begonnen — Sie können diese Sitzung fortsetzen."
      +    stopped:               "⚡ Gestoppt. Sie können diese Sitzung fortsetzen."
      +    no_active:             "Keine aktive Aufgabe zum Stoppen."
      +
      +  title:
      +    db_unavailable:        "Sitzungsdatenbank nicht verfügbar."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Titel ist nach der Bereinigung leer. Bitte druckbare Zeichen verwenden."
      +    set_to:                "✏️ Sitzungstitel gesetzt: **{title}**"
      +    not_found:             "Sitzung nicht in der Datenbank gefunden."
      +    current_with_title:    "📌 Sitzung: `{session_id}`\nTitel: **{title}**"
      +    current_no_title:      "📌 Sitzung: `{session_id}`\nKein Titel gesetzt. Verwendung: `/title Mein Sitzungsname`"
      +
      +  topic:
      +    not_telegram_dm:         "Der /topic-Befehl ist nur in Telegram-Privatchats verfügbar."
      +    no_session_db:           "Sitzungsdatenbank nicht verfügbar."
      +    unauthorized:            "Sie sind nicht berechtigt, /topic auf diesem Bot zu verwenden."
      +    restore_needs_topic:     "Um eine Sitzung wiederherzustellen, erstellen oder öffnen Sie zuerst ein Telegram-Topic und senden Sie dann /topic <session-id> innerhalb dieses Topics. Um ein neues Topic zu erstellen, öffnen Sie All Messages und senden Sie dort eine beliebige Nachricht."
      +    topics_disabled:         "Telegram-Topics sind für diesen Bot noch nicht aktiviert.\n\nSo aktivieren Sie sie:\n1. Öffnen Sie @BotFather.\n2. Wählen Sie Ihren Bot.\n3. Öffnen Sie Bot Settings → Threads Settings.\n4. Aktivieren Sie Threaded Mode und stellen Sie sicher, dass Benutzer neue Threads erstellen dürfen.\n\nDann senden Sie /topic erneut."
      +    topics_user_disallowed:  "Telegram-Topics sind aktiviert, aber Benutzer dürfen keine Topics erstellen.\n\nÖffnen Sie @BotFather → wählen Sie Ihren Bot → Bot Settings → Threads Settings, und deaktivieren Sie dann 'Disallow users to create new threads'.\n\nDann senden Sie /topic erneut."
      +    enable_failed:           "Telegram-Topic-Modus konnte nicht aktiviert werden: {error}"
      +    bound_status:            "Dieses Topic ist verknüpft mit:\nSitzung: {label}\nID: {session_id}\n\nVerwenden Sie /new, um dieses Topic durch eine neue Sitzung zu ersetzen.\nFür parallele Arbeit öffnen Sie All Messages und senden Sie dort eine Nachricht, um ein weiteres Topic zu erstellen."
      +    thread_ready:            "Telegram-Multi-Session-Topics sind aktiviert.\n\nDieses Topic wird als unabhängige Hermes-Sitzung verwendet. Verwenden Sie /new, um die aktuelle Sitzung dieses Topics zu ersetzen. Für parallele Arbeit öffnen Sie All Messages und senden Sie dort eine Nachricht, um ein weiteres Topic zu erstellen."
      +    untitled_session:        "Unbenannte Sitzung"
      +
      +  undo:
      +    nothing:               "Nichts zum Rückgängigmachen."
      +    removed:               "↩️ {count} Nachricht(en) rückgängig gemacht.\nEntfernt: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update ist nur auf Messaging-Plattformen verfügbar. Führen Sie `hermes update` im Terminal aus."
      +    not_git_repo:            "✗ Kein Git-Repository — Update nicht möglich."
      +    hermes_cmd_not_found:    "✗ Der Befehl `hermes` konnte nicht gefunden werden. Hermes läuft, aber der Update-Befehl konnte das ausführbare Programm weder im PATH noch über den aktuellen Python-Interpreter finden. Versuchen Sie, `hermes update` manuell im Terminal auszuführen."
      +    start_failed:            "✗ Update konnte nicht gestartet werden: {error}"
      +    starting:                "⚕ Hermes-Update wird gestartet… Ich streame den Fortschritt hier."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Ratenlimits:** {state}"
      +    header_session:           "📊 **Sitzungs-Token-Nutzung**"
      +    label_model:              "Modell: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] {count}"
      +    label_cache_read:         "Cache-Lesetokens: [redacted:secret-assignment]"
      +    label_cache_write:        "Cache-Schreibtokens: [redacted:secret-assignment]"
      +    label_output_tokens:      "[redacted:secret-assignment] {count}"
      +    label_total:              "Gesamt: {count}"
      +    label_api_calls:          "API-Aufrufe: {count}"
      +    label_cost:               "Kosten: {prefix}${amount}"
      +    label_cost_included:      "Kosten: inbegriffen"
      +    label_context:            "Kontext: {used} / {total} ({pct}%)"
      +    label_compressions:       "Kompressionen: {count}"
      +    header_session_info:      "📊 **Sitzungsinfo**"
      +    label_messages:           "Nachrichten: {count}"
      +    label_estimated_context:  "Geschätzter Kontext: ~{count} Tokens"
      +    detailed_after_first:     "_(Detaillierte Nutzung nach der ersten Agentenantwort verfügbar)_"
      +    no_data:                  "Keine Nutzungsdaten für diese Sitzung verfügbar."
      +
      +  verbose:
      +    not_enabled:           "Der Befehl `/verbose` ist für Messaging-Plattformen nicht aktiviert.\n\nIn `config.yaml` aktivieren:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Tool-Fortschritt: **OFF** — keine Tool-Aktivität angezeigt."
      +    mode_new:              "⚙️ Tool-Fortschritt: **NEW** — angezeigt bei Tool-Wechsel (Vorschaulänge: `display.tool_preview_length`, Standard 40)."
      +    mode_all:              "⚙️ Tool-Fortschritt: **ALL** — jeder Tool-Aufruf wird angezeigt (Vorschaulänge: `display.tool_preview_length`, Standard 40)."
      +    mode_verbose:          "⚙️ Tool-Fortschritt: **VERBOSE** — jeder Tool-Aufruf mit vollständigen Argumenten."
      +    saved_suffix:          "_(für **{platform}** gespeichert — wird ab nächster Nachricht wirksam)_"
      +    save_failed:           "_(konnte nicht in der Konfiguration gespeichert werden: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Sprachmodus aktiviert.\nIch antworte mit Sprache, wenn Sie Sprachnachrichten senden.\nVerwenden Sie /voice tts für Sprachantworten auf alle Nachrichten."
      +    disabled_text:         "Sprachmodus deaktiviert. Nur Textantworten."
      +    tts_enabled:           "Auto-TTS aktiviert.\nAlle Antworten enthalten eine Sprachnachricht."
      +    status_mode:           "Sprachmodus: {label}"
      +    status_channel:        "Sprachkanal: #{channel}"
      +    status_participants:   "Teilnehmer: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (spricht)"
      +    enabled_short:         "Sprachmodus aktiviert."
      +    disabled_short:        "Sprachmodus deaktiviert."
      +    label_off:             "Aus (nur Text)"
      +    label_voice_only:      "An (Sprachantwort auf Sprachnachrichten)"
      +    label_all:             "TTS (Sprachantwort auf alle Nachrichten)"
      +
      +  yolo:
      +    disabled:                   "⚠️ YOLO-Modus für diese Sitzung **AUS** — gefährliche Befehle benötigen eine Genehmigung."
      +    enabled:                    "⚡ YOLO-Modus für diese Sitzung **AN** — alle Befehle werden automatisch genehmigt. Mit Vorsicht verwenden."
      +
      +  shared:
      +    session_db_unavailable:      "Session-Datenbank nicht verfügbar."
      +    session_db_unavailable_prefix: "Session-Datenbank nicht verfügbar"
      +    session_not_found:           "Session nicht in der Datenbank gefunden."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/en.yaml
      @@ -33,3 +33,333 @@ gateway:
         no_active_goal:   "No active goal."
         config_read_failed: "⚠️ Could not read config.yaml: {error}"
         config_save_failed: "⚠️ Could not save config: {error}"
      +
      +  # /model command output -- shown after a model switch or when listing models.
      +  # Provider names, model IDs, capability strings, and cost figures are NOT
      +  # translated -- they're identifiers/values, not prose.  Only the labels
      +  # ("Provider:", "Context:", etc.) and the help/footer lines are localized.
      +  model:
      +    error_prefix:           "Error: {error}"
      +    switched:               "Model switched to `{model}`"
      +    provider_label:         "Provider: {provider}"
      +    context_label:          "Context: {tokens} tokens"
      +    max_output_label:       "Max output: {tokens} tokens"
      +    cost_label:             "Cost: {cost}"
      +    capabilities_label:     "Capabilities: {capabilities}"
      +    prompt_caching_enabled: "Prompt caching: enabled"
      +    warning_prefix:         "Warning: {warning}"
      +    saved_global:           "Saved to config.yaml (`--global`)"
      +    session_only_hint:      "_(session only — add `--global` to persist)_"
      +    current_label:          "Current: `{model}` on {provider}"
      +    current_tag:            " (current)"
      +    more_models_suffix:     " (+{count} more)"
      +    usage_switch_model:     "`/model <name>` — switch model"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — switch provider"
      +    usage_persist:          "`/model <name> --global` — persist"
      +
      +  agents:
      +    header:                "🤖 **Active Agents & Tasks**"
      +    active_agents:         "**Active agents:** {count}"
      +    this_chat:             " · this chat"
      +    more:                  "... and {count} more"
      +    running_processes:     "**Running background processes:** {count}"
      +    async_jobs:            "**Gateway async jobs:** {count}"
      +    none:                  "No active agents or running tasks."
      +    state_starting:        "starting"
      +    state_running:         "running"
      +
      +  approve:
      +    no_pending:            "No pending command to approve."
      +    once_singular:         "✅ Command approved. The agent is resuming..."
      +    once_plural:           "✅ Commands approved ({count} commands). The agent is resuming..."
      +    session_singular:      "✅ Command approved (pattern approved for this session). The agent is resuming..."
      +    session_plural:        "✅ Commands approved (pattern approved for this session) ({count} commands). The agent is resuming..."
      +    always_singular:       "✅ Command approved (pattern approved permanently). The agent is resuming..."
      +    always_plural:         "✅ Commands approved (pattern approved permanently) ({count} commands). The agent is resuming..."
      +
      +  background:
      +    usage:                 "Usage: /background <prompt>\nExample: /background Summarize the top HN stories today\n\nRuns the prompt in a separate session. You can keep chatting — the result will appear here when done."
      +    started:               "🔄 Background task started: \"{preview}\"\nTask ID: {task_id}\nYou can keep chatting — results will appear when done."
      +
      +  branch:
      +    db_unavailable:        "Session database not available."
      +    no_conversation:       "No conversation to branch — send a message first."
      +    create_failed:         "Failed to create branch: {error}"
      +    switch_failed:         "Branch created but failed to switch to it."
      +    branched_one:          "⑂ Branched to **{title}** ({count} message copied)\nOriginal: `{parent}`\nBranch: `{new}`\nUse `/resume` to switch back to the original."
      +    branched_many:         "⑂ Branched to **{title}** ({count} messages copied)\nOriginal: `{parent}`\nBranch: `{new}`\nUse `/resume` to switch back to the original."
      +
      +  commands:
      +    usage:                 "Usage: `/commands [page]`"
      +    skill_header:          "⚡ **Skill Commands**:"
      +    default_desc:          "Skill command"
      +    none:                  "No commands available."
      +    header:                "📚 **Commands** ({total} total, page {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← prev"
      +    nav_next:              "next → `/commands {page}`"
      +    out_of_range:          "_(Requested page {requested} was out of range, showing page {page}.)_"
      +
      +  compress:
      +    not_enough:            "Not enough conversation to compress (need at least 4 messages)."
      +    no_provider:           "No provider configured -- cannot compress."
      +    nothing_to_do:         "Nothing to compress yet (the transcript is still all protected context)."
      +    focus_line:            "Focus: \"{topic}\""
      +    summary_failed:        "⚠️ Summary generation failed ({error}). {count} historical message(s) were removed and replaced with a placeholder; earlier context is no longer recoverable. Consider checking your auxiliary.compression model configuration."
      +    aux_failed:            "ℹ️ Configured compression model `{model}` failed ({error}). Recovered using your main model — context is intact — but you may want to check `auxiliary.compression.model` in config.yaml."
      +    failed:                "Compression failed: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Failed to upload debug report: {error}"
      +    header:                "**Debug report uploaded:**"
      +    auto_delete:           "⏱ Pastes will auto-delete in 6 hours."
      +    full_logs_hint:        "For full log uploads, use `hermes debug share` from the CLI."
      +    share_hint:            "Share these links with the Hermes team for support."
      +
      +  deny:
      +    stale:                 "❌ Command denied (approval was stale)."
      +    no_pending:            "No pending command to deny."
      +    denied_singular:       "❌ Command denied."
      +    denied_plural:         "❌ Commands denied ({count} commands)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast is only available for OpenAI models that support Priority Processing."
      +    status:                "⚡ Priority Processing\n\nCurrent mode: `{mode}`\n\n_Usage:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Unknown argument: `{arg}`\n\n**Valid options:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (saved to config)\n_(takes effect on next message)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (this session only)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Runtime footer: **{state}**\nFields: `{fields}`\nPlatform: `{platform}`"
      +    usage:                 "Usage: `/footer [on|off|status]`"
      +    saved:                 "📎 Runtime footer: **{state}**{example}\n_(saved globally — takes effect on next message)_"
      +    example_line:          "\nExample: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Goals unavailable on this session."
      +    no_goal_set:           "No goal set."
      +    paused:                "⏸ Goal paused: {goal}"
      +    no_resume:             "No goal to resume."
      +    resumed:               "▶ Goal resumed: {goal}\nSend any message to continue, or wait — I'll take the next step on the next turn."
      +    invalid:               "Invalid goal: {error}"
      +    set:                   "⊙ Goal set ({budget}-turn budget): {goal}\nI'll keep working until the goal is done, you pause/clear it, or the budget is exhausted.\nControls: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes Commands**\n"
      +    skill_header:          "\n⚡ **Skill Commands** ({count} active):"
      +    more_use_commands:     "\n... and {count} more. Use `/commands` for the full paginated list."
      +
      +  insights:
      +    invalid_days:          "Invalid --days value: {value}"
      +    error:                 "Error generating insights: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban error: {error}"
      +    subscribed_suffix:     "(subscribed — you'll be notified when {task_id} completes or blocks)"
      +    truncated_suffix:      "… (truncated; use `hermes kanban …` in your terminal for full output)"
      +    no_output:             "(no output)"
      +
      +  personality:
      +    none_configured:       "No personalities configured in `{path}/config.yaml`"
      +    header:                "🎭 **Available Personalities**\n"
      +    none_option:           "• `none` — (no personality overlay)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nUsage: `/personality <name>`"
      +    save_failed:           "⚠️ Failed to save personality change: {error}"
      +    cleared:               "🎭 Personality cleared — using base agent behavior.\n_(takes effect on next message)_"
      +    set_to:                "🎭 Personality set to **{name}**\n_(takes effect on next message)_"
      +    unknown:               "Unknown personality: `{name}`\n\nAvailable: {available}"
      +
      +  profile:
      +    header:                "👤 **Profile:** `{profile}`"
      +    home:                  "📂 **Home:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (default)"
      +    level_disabled:            "none (disabled)"
      +    scope_session:             "session override"
      +    scope_global:              "global config"
      +    status:                    "🧠 **Reasoning Settings**\n\n**Effort:** `{level}`\n**Scope:** {scope}\n**Display:** {display}\n\n_Usage:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "on ✓"
      +    display_off:               "off"
      +    display_set_on:            "🧠 ✓ Reasoning display: **ON**\nModel thinking will be shown before each response on **{platform}**."
      +    display_set_off:           "🧠 ✓ Reasoning display: **OFF** for **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` is not supported. Use `/reasoning <level> --global` to change the global default."
      +    reset_done:                "🧠 ✓ Session reasoning override cleared; falling back to global config."
      +    unknown_arg:               "⚠️ Unknown argument: `{arg}`\n\n**Valid levels:** none, minimal, low, medium, high, xhigh\n**Display:** show, hide\n**Persist:** add `--global` to save beyond this session"
      +    set_global:                "🧠 ✓ Reasoning effort set to `{effort}` (saved to config)\n_(takes effect on next message)_"
      +    set_global_save_failed:    "🧠 ✓ Reasoning effort set to `{effort}` (session only — config save failed)\n_(takes effect on next message)_"
      +    set_session:               "🧠 ✓ Reasoning effort set to `{effort}` (session only — add `--global` to persist)\n_(takes effect on next message)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp cancelled. MCP tools unchanged."
      +    always_followup:       "ℹ️ Future `/reload-mcp` calls will run without confirmation. Re-enable via `approvals.mcp_reload_confirm: true` in config.yaml."
      +    confirm_prompt:        "⚠️ **Confirm /reload-mcp**\n\nReloading MCP servers rebuilds the tool set for this session and **invalidates the provider prompt cache** — the next message will re-send full input tokens.  On long-context or high-reasoning models this can be expensive.\n\nChoose:\n• **Approve Once** — reload now\n• **Always Approve** — reload now and silence this prompt permanently\n• **Cancel** — leave MCP tools unchanged\n\n_Text fallback: reply `/approve`, `/always`, or `/cancel`._"
      +    header:                "🔄 **MCP Servers Reloaded**\n"
      +    reconnected:           "♻️ Reconnected: {names}"
      +    added:                 "➕ Added: {names}"
      +    removed:               "➖ Removed: {names}"
      +    none_connected:        "No MCP servers connected."
      +    tools_available:       "\n🔧 {tools} tool(s) available from {servers} server(s)"
      +    failed:                "❌ MCP reload failed: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skills Reloaded**\n"
      +    no_new:                "No new skills detected."
      +    total:                 "\n📚 {count} skill(s) available"
      +    added_header:          "➕ **Added Skills:**"
      +    removed_header:        "➖ **Removed Skills:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Skills reload failed: {error}"
      +
      +  reset:
      +    header_default:        "✨ Session reset! Starting fresh."
      +    header_new:            "✨ New session started!"
      +    header_titled:         "✨ New session started: {title}"
      +    title_rejected:        "\n⚠️ Title rejected: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — session started untitled."
      +    title_empty_untitled:  "\n⚠️ Title is empty after cleanup — session started untitled."
      +    tip:                   "\n✦ Tip: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Gateway restart already in progress..."
      +    restarting:            "♻ Restarting gateway. If you aren't notified within 60 seconds, restart from the console with `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Session database not available."
      +    no_named_sessions:     "No named sessions found.\nUse `/title My Session` to name your current session, then `/resume My Session` to return to it later."
      +    list_header:           "📋 **Named Sessions**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nUsage: `/resume <session name>`"
      +    list_failed:           "Could not list sessions: {error}"
      +    not_found:             "No session found matching '**{name}**'.\nUse `/resume` with no arguments to see available sessions."
      +    already_on:            "📌 Already on session **{name}**."
      +    switch_failed:         "Failed to switch session."
      +    resumed_one:           "↻ Resumed session **{title}** ({count} message). Conversation restored."
      +    resumed_many:          "↻ Resumed session **{title}** ({count} messages). Conversation restored."
      +    resumed_no_count:      "↻ Resumed session **{title}**. Conversation restored."
      +
      +  retry:
      +    no_previous:           "No previous message to retry."
      +
      +  rollback:
      +    not_enabled:           "Checkpoints are not enabled.\nEnable in config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "No checkpoints found for {cwd}"
      +    invalid_number:        "Invalid checkpoint number. Use 1-{max}."
      +    restored:              "✅ Restored to checkpoint {hash}: {reason}\nA pre-rollback snapshot was saved automatically."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Failed to save home channel: {error}"
      +    success:               "✅ Home channel set to **{name}** (ID: {chat_id}).\nCron jobs and cross-platform messages will be delivered here."
      +
      +  status:
      +    header:                "📊 **Hermes Gateway Status**"
      +    session_id:            "**Session ID:** `{session_id}`"
      +    title:                 "**Title:** {title}"
      +    created:               "**Created:** {timestamp}"
      +    last_activity:         "**Last Activity:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agent Running:** {state}"
      +    state_yes:             "Yes ⚡"
      +    state_no:              "No"
      +    queued:                "**Queued follow-ups:** {count}"
      +    platforms:             "**Connected Platforms:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Stopped. The agent hadn't started yet — you can continue this session."
      +    stopped:               "⚡ Stopped. You can continue this session."
      +    no_active:             "No active task to stop."
      +
      +  title:
      +    db_unavailable:        "Session database not available."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Title is empty after cleanup. Please use printable characters."
      +    set_to:                "✏️ Session title set: **{title}**"
      +    not_found:             "Session not found in database."
      +    current_with_title:    "📌 Session: `{session_id}`\nTitle: **{title}**"
      +    current_no_title:      "📌 Session: `{session_id}`\nNo title set. Usage: `/title My Session Name`"
      +
      +  topic:
      +    not_telegram_dm:         "The /topic command is only available in Telegram private chats."
      +    no_session_db:           "Session database not available."
      +    unauthorized:            "You are not authorized to use /topic on this bot."
      +    restore_needs_topic:     "To restore a session, first create or open a Telegram topic, then send /topic <session-id> inside that topic. To create a new topic, open All Messages and send any message there."
      +    topics_disabled:         "Telegram topics are not enabled for this bot yet.\n\nHow to enable them:\n1. Open @BotFather.\n2. Choose your bot.\n3. Open Bot Settings → Threads Settings.\n4. Turn on Threaded Mode and make sure users are allowed to create new threads.\n\nThen send /topic again."
      +    topics_user_disallowed:  "Telegram topics are enabled, but users are not allowed to create topics.\n\nOpen @BotFather → choose your bot → Bot Settings → Threads Settings, then turn off 'Disallow users to create new threads'.\n\nThen send /topic again."
      +    enable_failed:           "Failed to enable Telegram topic mode: {error}"
      +    bound_status:            "This topic is linked to:\nSession: {label}\nID: {session_id}\n\nUse /new to replace this topic with a fresh session.\nFor parallel work, open All Messages and send a message there to create another topic."
      +    thread_ready:            "Telegram multi-session topics are enabled.\n\nThis topic will be used as an independent Hermes session. Use /new to replace this topic's current session. For parallel work, open All Messages and send a message there to create another topic."
      +    untitled_session:        "Untitled session"
      +
      +  undo:
      +    nothing:               "Nothing to undo."
      +    removed:               "↩️ Undid {count} message(s).\nRemoved: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update is only available from messaging platforms. Run `hermes update` from the terminal."
      +    not_git_repo:            "✗ Not a git repository — cannot update."
      +    hermes_cmd_not_found:    "✗ Could not locate the `hermes` command. Hermes is running, but the update command could not find the executable on PATH or via the current Python interpreter. Try running `hermes update` manually in your terminal."
      +    start_failed:            "✗ Failed to start update: {error}"
      +    starting:                "⚕ Starting Hermes update… I'll stream progress here."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Rate Limits:** {state}"
      +    header_session:           "📊 **Session Token Usage**"
      +    label_model:              "Model: `{model}`"
      +    label_input_tokens:       "Input tokens: [redacted:secret-assignment]"
      +    label_cache_read:         "Cache read tokens: [redacted:secret-assignment]"
      +    label_cache_write:        "Cache write tokens: [redacted:secret-assignment]"
      +    label_output_tokens:      "[redacted:secret-assignment] tokens: [redacted:secret-assignment]"
      +    label_total:              "Total: {count}"
      +    label_api_calls:          "API calls: {count}"
      +    label_cost:               "Cost: {prefix}${amount}"
      +    label_cost_included:      "Cost: included"
      +    label_context:            "Context: {used} / {total} ({pct}%)"
      +    label_compressions:       "Compressions: {count}"
      +    header_session_info:      "📊 **Session Info**"
      +    label_messages:           "Messages: {count}"
      +    label_estimated_context:  "Estimated context: ~{count} tokens"
      +    detailed_after_first:     "_(Detailed usage available after the first agent response)_"
      +    no_data:                  "No usage data available for this session."
      +
      +  verbose:
      +    not_enabled:           "The `/verbose` command is not enabled for messaging platforms.\n\nEnable it in `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Tool progress: **OFF** — no tool activity shown."
      +    mode_new:              "⚙️ Tool progress: **NEW** — shown when tool changes (preview length: `display.tool_preview_length`, default 40)."
      +    mode_all:              "⚙️ Tool progress: **ALL** — every tool call shown (preview length: `display.tool_preview_length`, default 40)."
      +    mode_verbose:          "⚙️ Tool progress: **VERBOSE** — every tool call with full arguments."
      +    saved_suffix:          "_(saved for **{platform}** — takes effect on next message)_"
      +    save_failed:           "_(could not save to config: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Voice mode enabled.\nI'll reply with voice when you send voice messages.\nUse /voice tts to get voice replies for all messages."
      +    disabled_text:         "Voice mode disabled. Text-only replies."
      +    tts_enabled:           "Auto-TTS enabled.\nAll replies will include a voice message."
      +    status_mode:           "Voice mode: {label}"
      +    status_channel:        "Voice channel: #{channel}"
      +    status_participants:   "Participants: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (speaking)"
      +    enabled_short:         "Voice mode enabled."
      +    disabled_short:        "Voice mode disabled."
      +    label_off:             "Off (text only)"
      +    label_voice_only:      "On (voice reply to voice messages)"
      +    label_all:             "TTS (voice reply to all messages)"
      +
      +  yolo:
      +    disabled:                   "⚠️ YOLO mode **OFF** for this session — dangerous commands will require approval."
      +    enabled:                    "⚡ YOLO mode **ON** for this session — all commands auto-approved. Use with caution."
      +
      +  shared:
      +    session_db_unavailable:      "Session database not available."
      +    session_db_unavailable_prefix: "Session database not available"
      +    session_not_found:           "Session not found in database."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/es.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "No hay objetivo activo."
         config_read_failed: "⚠️ No se pudo leer config.yaml: {error}"
         config_save_failed: "⚠️ No se pudo guardar la configuración: {error}"
      +
      +  model:
      +    error_prefix:           "Error: {error}"
      +    switched:               "Modelo cambiado a `{model}`"
      +    provider_label:         "Proveedor: {provider}"
      +    context_label:          "Contexto: {tokens} tokens"
      +    max_output_label:       "Salida máxima: {tokens} tokens"
      +    cost_label:             "Coste: {cost}"
      +    capabilities_label:     "Capacidades: {capabilities}"
      +    prompt_caching_enabled: "Caché de prompts: activado"
      +    warning_prefix:         "Advertencia: {warning}"
      +    saved_global:           "Guardado en config.yaml (`--global`)"
      +    session_only_hint:      "_(solo para esta sesión — añade `--global` para guardarlo)_"
      +    current_label:          "Actual: `{model}` en {provider}"
      +    current_tag:            " (actual)"
      +    more_models_suffix:     " (+{count} más)"
      +    usage_switch_model:     "`/model <name>` — cambiar modelo"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — cambiar proveedor"
      +    usage_persist:          "`/model <name> --global` — guardar de forma permanente"
      +
      +  agents:
      +    header:                "🤖 **Agentes y tareas activos**"
      +    active_agents:         "**Agentes activos:** {count}"
      +    this_chat:             " · este chat"
      +    more:                  "... y {count} más"
      +    running_processes:     "**Procesos en segundo plano en ejecución:** {count}"
      +    async_jobs:            "**Tareas asíncronas del gateway:** {count}"
      +    none:                  "No hay agentes activos ni tareas en ejecución."
      +    state_starting:        "iniciando"
      +    state_running:         "en ejecución"
      +
      +  approve:
      +    no_pending:            "No hay ningún comando pendiente que aprobar."
      +    once_singular:         "✅ Comando aprobado. El agente se está reanudando..."
      +    once_plural:           "✅ Comandos aprobados ({count} comandos). El agente se está reanudando..."
      +    session_singular:      "✅ Comando aprobado (patrón aprobado para esta sesión). El agente se está reanudando..."
      +    session_plural:        "✅ Comandos aprobados (patrón aprobado para esta sesión) ({count} comandos). El agente se está reanudando..."
      +    always_singular:       "✅ Comando aprobado (patrón aprobado permanentemente). El agente se está reanudando..."
      +    always_plural:         "✅ Comandos aprobados (patrón aprobado permanentemente) ({count} comandos). El agente se está reanudando..."
      +
      +  background:
      +    usage:                 "Uso: /background <prompt>\nEjemplo: /background Resume las principales historias de HN de hoy\n\nEjecuta el prompt en una sesión separada. Puedes seguir chateando — el resultado aparecerá aquí cuando termine."
      +    started:               "🔄 Tarea en segundo plano iniciada: \"{preview}\"\nID de tarea: {task_id}\nPuedes seguir chateando — los resultados aparecerán aquí cuando terminen."
      +
      +  branch:
      +    db_unavailable:        "Base de datos de sesiones no disponible."
      +    no_conversation:       "No hay conversación para ramificar — envía un mensaje primero."
      +    create_failed:         "No se pudo crear la rama: {error}"
      +    switch_failed:         "Rama creada pero no se pudo cambiar a ella."
      +    branched_one:          "⑂ Ramificado a **{title}** ({count} mensaje copiado)\nOriginal: `{parent}`\nRama: `{new}`\nUsa `/resume` para volver al original."
      +    branched_many:         "⑂ Ramificado a **{title}** ({count} mensajes copiados)\nOriginal: `{parent}`\nRama: `{new}`\nUsa `/resume` para volver al original."
      +
      +  commands:
      +    usage:                 "Uso: `/commands [page]`"
      +    skill_header:          "⚡ **Comandos de skill**:"
      +    default_desc:          "Comando de skill"
      +    none:                  "No hay comandos disponibles."
      +    header:                "📚 **Comandos** ({total} en total, página {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← anterior"
      +    nav_next:              "siguiente → `/commands {page}`"
      +    out_of_range:          "_(La página solicitada {requested} estaba fuera de rango, mostrando la página {page}.)_"
      +
      +  compress:
      +    not_enough:            "No hay suficiente conversación para comprimir (se necesitan al menos 4 mensajes)."
      +    no_provider:           "No hay proveedor configurado — no se puede comprimir."
      +    nothing_to_do:         "Aún no hay nada que comprimir (la transcripción sigue siendo todo contexto protegido)."
      +    focus_line:            "Enfoque: \"{topic}\""
      +    summary_failed:        "⚠️ Falló la generación del resumen ({error}). Se eliminaron {count} mensaje(s) históricos y se reemplazaron por un marcador; el contexto anterior ya no se puede recuperar. Considera revisar la configuración del modelo auxiliary.compression."
      +    aux_failed:            "ℹ️ El modelo de compresión configurado `{model}` falló ({error}). Recuperado con tu modelo principal — el contexto está intacto — pero quizá quieras revisar `auxiliary.compression.model` en config.yaml."
      +    failed:                "Compresión fallida: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ No se pudo subir el informe de depuración: {error}"
      +    header:                "**Informe de depuración subido:**"
      +    auto_delete:           "⏱ Los pastes se eliminarán automáticamente en 6 horas."
      +    full_logs_hint:        "Para subir registros completos, usa `hermes debug share` desde la CLI."
      +    share_hint:            "Comparte estos enlaces con el equipo de Hermes para obtener soporte."
      +
      +  deny:
      +    stale:                 "❌ Comando denegado (la aprobación había caducado)."
      +    no_pending:            "No hay ningún comando pendiente que denegar."
      +    denied_singular:       "❌ Comando denegado."
      +    denied_plural:         "❌ Comandos denegados ({count} comandos)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast solo está disponible para modelos de OpenAI que admiten Priority Processing."
      +    status:                "⚡ Priority Processing\n\nModo actual: `{mode}`\n\n_Uso:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Argumento desconocido: `{arg}`\n\n**Opciones válidas:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (guardado en la configuración)\n_(se aplica en el próximo mensaje)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (solo esta sesión)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Pie de ejecución: **{state}**\nCampos: `{fields}`\nPlataforma: `{platform}`"
      +    usage:                 "Uso: `/footer [on|off|status]`"
      +    saved:                 "📎 Pie de ejecución: **{state}**{example}\n_(guardado globalmente — se aplica en el próximo mensaje)_"
      +    example_line:          "\nEjemplo: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Los objetivos no están disponibles en esta sesión."
      +    no_goal_set:           "No hay objetivo establecido."
      +    paused:                "⏸ Objetivo pausado: {goal}"
      +    no_resume:             "No hay objetivo para reanudar."
      +    resumed:               "▶ Objetivo reanudado: {goal}\nEnvía cualquier mensaje para continuar, o espera — daré el siguiente paso en el próximo turno."
      +    invalid:               "Objetivo no válido: {error}"
      +    set:                   "⊙ Objetivo establecido (presupuesto de {budget} turnos): {goal}\nSeguiré trabajando hasta que el objetivo se complete, lo pauses/elimines o se agote el presupuesto.\nControles: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Comandos de Hermes**\n"
      +    skill_header:          "\n⚡ **Comandos de skill** ({count} activos):"
      +    more_use_commands:     "\n... y {count} más. Usa `/commands` para la lista paginada completa."
      +
      +  insights:
      +    invalid_days:          "Valor --days no válido: {value}"
      +    error:                 "Error al generar el análisis: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ error de kanban: {error}"
      +    subscribed_suffix:     "(suscrito — recibirás una notificación cuando {task_id} termine o se bloquee)"
      +    truncated_suffix:      "… (truncado; usa `hermes kanban …` en tu terminal para la salida completa)"
      +    no_output:             "(sin salida)"
      +
      +  personality:
      +    none_configured:       "No hay personalidades configuradas en `{path}/config.yaml`"
      +    header:                "🎭 **Personalidades disponibles**\n"
      +    none_option:           "• `none` — (sin superposición de personalidad)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nUso: `/personality <name>`"
      +    save_failed:           "⚠️ No se pudo guardar el cambio de personalidad: {error}"
      +    cleared:               "🎭 Personalidad eliminada — usando el comportamiento base del agente.\n_(surte efecto en el siguiente mensaje)_"
      +    set_to:                "🎭 Personalidad establecida en **{name}**\n_(surte efecto en el siguiente mensaje)_"
      +    unknown:               "Personalidad desconocida: `{name}`\n\nDisponibles: {available}"
      +
      +  profile:
      +    header:                "👤 **Perfil:** `{profile}`"
      +    home:                  "📂 **Inicio:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (predeterminado)"
      +    level_disabled:            "none (deshabilitado)"
      +    scope_session:             "anulación de sesión"
      +    scope_global:              "configuración global"
      +    status:                    "🧠 **Ajustes de razonamiento**\n\n**Esfuerzo:** `{level}`\n**Alcance:** {scope}\n**Visualización:** {display}\n\n_Uso:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "activada ✓"
      +    display_off:               "desactivada"
      +    display_set_on:            "🧠 ✓ Visualización de razonamiento: **ACTIVADA**\nEl pensamiento del modelo se mostrará antes de cada respuesta en **{platform}**."
      +    display_set_off:           "🧠 ✓ Visualización de razonamiento: **DESACTIVADA** para **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` no es compatible. Usa `/reasoning <level> --global` para cambiar el valor global por defecto."
      +    reset_done:                "🧠 ✓ Anulación de razonamiento de la sesión borrada; volviendo a la configuración global."
      +    unknown_arg:               "⚠️ Argumento desconocido: `{arg}`\n\n**Niveles válidos:** none, minimal, low, medium, high, xhigh\n**Visualización:** show, hide\n**Persistir:** añade `--global` para guardar más allá de esta sesión"
      +    set_global:                "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (guardado en la configuración)\n_(se aplica en el próximo mensaje)_"
      +    set_global_save_failed:    "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (solo en la sesión — error al guardar la configuración)\n_(se aplica en el próximo mensaje)_"
      +    set_session:               "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (solo en la sesión — añade `--global` para persistir)\n_(se aplica en el próximo mensaje)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp cancelado. Las herramientas MCP no han cambiado."
      +    always_followup:       "ℹ️ Las próximas llamadas a `/reload-mcp` se ejecutarán sin confirmación. Reactiva mediante `approvals.mcp_reload_confirm: true` en `config.yaml`."
      +    confirm_prompt:        "⚠️ **Confirmar /reload-mcp**\n\nRecargar los servidores MCP reconstruye el conjunto de herramientas de esta sesión e **invalida la caché de prompt del proveedor** — el siguiente mensaje reenviará los tokens de entrada completos. En modelos de contexto largo o de razonamiento alto esto puede resultar costoso.\n\nElige:\n• **Aprobar una vez** — recargar ahora\n• **Aprobar siempre** — recargar ahora y silenciar esta confirmación permanentemente\n• **Cancelar** — dejar las herramientas MCP sin cambios\n\n_Alternativa de texto: responde `/approve`, `/always` o `/cancel`._"
      +    header:                "🔄 **Servidores MCP recargados**\n"
      +    reconnected:           "♻️ Reconectados: {names}"
      +    added:                 "➕ Añadidos: {names}"
      +    removed:               "➖ Eliminados: {names}"
      +    none_connected:        "No hay servidores MCP conectados."
      +    tools_available:       "\n🔧 {tools} herramienta(s) disponibles de {servers} servidor(es)"
      +    failed:                "❌ Falló la recarga de MCP: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skills recargadas**\n"
      +    no_new:                "No se detectaron nuevas skills."
      +    total:                 "\n📚 {count} skill(s) disponibles"
      +    added_header:          "➕ **Skills añadidas:**"
      +    removed_header:        "➖ **Skills eliminadas:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Falló la recarga de skills: {error}"
      +
      +  reset:
      +    header_default:        "✨ ¡Sesión reiniciada! Empezando de nuevo."
      +    header_new:            "✨ ¡Nueva sesión iniciada!"
      +    header_titled:         "✨ Nueva sesión iniciada: {title}"
      +    title_rejected:        "\n⚠️ Título rechazado: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — sesión iniciada sin título."
      +    title_empty_untitled:  "\n⚠️ El título queda vacío tras la limpieza — sesión iniciada sin título."
      +    tip:                   "\n✦ Consejo: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ El reinicio del gateway ya está en curso..."
      +    restarting:            "♻ Reiniciando el gateway. Si no recibes notificación en 60 segundos, reinicia desde la consola con `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Base de datos de sesiones no disponible."
      +    no_named_sessions:     "No se encontraron sesiones con nombre.\nUsa `/title Mi sesión` para nombrar la sesión actual y luego `/resume Mi sesión` para volver a ella."
      +    list_header:           "📋 **Sesiones con nombre**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nUso: `/resume <nombre de sesión>`"
      +    list_failed:           "No se pudieron listar las sesiones: {error}"
      +    not_found:             "No se encontró ninguna sesión que coincida con '**{name}**'.\nUsa `/resume` sin argumentos para ver las sesiones disponibles."
      +    already_on:            "📌 Ya estás en la sesión **{name}**."
      +    switch_failed:         "No se pudo cambiar de sesión."
      +    resumed_one:           "↻ Sesión **{title}** reanudada ({count} mensaje). Conversación restaurada."
      +    resumed_many:          "↻ Sesión **{title}** reanudada ({count} mensajes). Conversación restaurada."
      +    resumed_no_count:      "↻ Sesión **{title}** reanudada. Conversación restaurada."
      +
      +  retry:
      +    no_previous:           "No hay un mensaje anterior para reintentar."
      +
      +  rollback:
      +    not_enabled:           "Los checkpoints no están habilitados.\nHabilítalos en config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "No se encontraron checkpoints para {cwd}"
      +    invalid_number:        "Número de checkpoint inválido. Usa 1-{max}."
      +    restored:              "✅ Restaurado al checkpoint {hash}: {reason}\nSe guardó automáticamente un snapshot previo al rollback."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "No se pudo guardar el canal principal: {error}"
      +    success:               "✅ Canal principal establecido en **{name}** (ID: {chat_id}).\nLas tareas cron y los mensajes entre plataformas se entregarán aquí."
      +
      +  status:
      +    header:                "📊 **Estado de Hermes Gateway**"
      +    session_id:            "**ID de sesión:** `{session_id}`"
      +    title:                 "**Título:** {title}"
      +    created:               "**Creado:** {timestamp}"
      +    last_activity:         "**Última actividad:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agente activo:** {state}"
      +    state_yes:             "Sí ⚡"
      +    state_no:              "No"
      +    queued:                "**Seguimientos en cola:** {count}"
      +    platforms:             "**Plataformas conectadas:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Detenido. El agente aún no había comenzado — puedes continuar esta sesión."
      +    stopped:               "⚡ Detenido. Puedes continuar esta sesión."
      +    no_active:             "No hay ninguna tarea activa que detener."
      +
      +  title:
      +    db_unavailable:        "Base de datos de sesiones no disponible."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ El título está vacío tras la limpieza. Usa caracteres imprimibles."
      +    set_to:                "✏️ Título de sesión establecido: **{title}**"
      +    not_found:             "Sesión no encontrada en la base de datos."
      +    current_with_title:    "📌 Sesión: `{session_id}`\nTítulo: **{title}**"
      +    current_no_title:      "📌 Sesión: `{session_id}`\nSin título. Uso: `/title Mi nombre de sesión`"
      +
      +  topic:
      +    not_telegram_dm:         "El comando /topic solo está disponible en chats privados de Telegram."
      +    no_session_db:           "Base de datos de sesiones no disponible."
      +    unauthorized:            "No tienes autorización para usar /topic en este bot."
      +    restore_needs_topic:     "Para restaurar una sesión, primero crea o abre un topic de Telegram, luego envía /topic <session-id> dentro de ese topic. Para crear un topic nuevo, abre All Messages y envía cualquier mensaje allí."
      +    topics_disabled:         "Los topics de Telegram aún no están habilitados para este bot.\n\nCómo habilitarlos:\n1. Abre @BotFather.\n2. Elige tu bot.\n3. Abre Bot Settings → Threads Settings.\n4. Activa Threaded Mode y asegúrate de permitir que los usuarios creen nuevos threads.\n\nLuego envía /topic de nuevo."
      +    topics_user_disallowed:  "Los topics de Telegram están habilitados, pero los usuarios no pueden crearlos.\n\nAbre @BotFather → elige tu bot → Bot Settings → Threads Settings, luego desactiva 'Disallow users to create new threads'.\n\nLuego envía /topic de nuevo."
      +    enable_failed:           "No se pudo habilitar el modo topic de Telegram: {error}"
      +    bound_status:            "Este topic está vinculado a:\nSesión: {label}\nID: {session_id}\n\nUsa /new para reemplazar este topic con una sesión nueva.\nPara trabajo paralelo, abre All Messages y envía un mensaje allí para crear otro topic."
      +    thread_ready:            "Los topics multisesión de Telegram están habilitados.\n\nEste topic se usará como una sesión independiente de Hermes. Usa /new para reemplazar la sesión actual de este topic. Para trabajo paralelo, abre All Messages y envía un mensaje allí para crear otro topic."
      +    untitled_session:        "Sesión sin título"
      +
      +  undo:
      +    nothing:               "Nada que deshacer."
      +    removed:               "↩️ {count} mensaje(s) deshecho(s).\nEliminado: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update solo está disponible en plataformas de mensajería. Ejecuta `hermes update` desde la terminal."
      +    not_git_repo:            "✗ No es un repositorio git — no se puede actualizar."
      +    hermes_cmd_not_found:    "✗ No se pudo localizar el comando `hermes`. Hermes está en ejecución, pero el comando de actualización no encontró el ejecutable en PATH ni a través del intérprete de Python actual. Intenta ejecutar `hermes update` manualmente en tu terminal."
      +    start_failed:            "✗ No se pudo iniciar la actualización: {error}"
      +    starting:                "⚕ Iniciando la actualización de Hermes… Transmitiré el progreso aquí."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Límites de tasa:** {state}"
      +    header_session:           "📊 **Uso de tokens de la sesión**"
      +    label_model:              "Modelo: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] de entrada: {count}"
      +    label_cache_read:         "Tokens de lectura de caché: {count}"
      +    label_cache_write:        "Tokens de escritura de caché: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] de salida: {count}"
      +    label_total:              "Total: {count}"
      +    label_api_calls:          "Llamadas API: {count}"
      +    label_cost:               "Costo: {prefix}${amount}"
      +    label_cost_included:      "Costo: incluido"
      +    label_context:            "Contexto: {used} / {total} ({pct}%)"
      +    label_compressions:       "Compresiones: {count}"
      +    header_session_info:      "📊 **Información de la sesión**"
      +    label_messages:           "Mensajes: {count}"
      +    label_estimated_context:  "Contexto estimado: ~{count} tokens"
      +    detailed_after_first:     "_(Uso detallado disponible tras la primera respuesta del agente)_"
      +    no_data:                  "No hay datos de uso disponibles para esta sesión."
      +
      +  verbose:
      +    not_enabled:           "El comando `/verbose` no está habilitado para plataformas de mensajería.\n\nHabilítalo en `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Progreso de herramientas: **OFF** — no se muestra actividad de herramientas."
      +    mode_new:              "⚙️ Progreso de herramientas: **NEW** — se muestra al cambiar de herramienta (longitud de vista previa: `display.tool_preview_length`, por defecto 40)."
      +    mode_all:              "⚙️ Progreso de herramientas: **ALL** — se muestra cada llamada a herramienta (longitud de vista previa: `display.tool_preview_length`, por defecto 40)."
      +    mode_verbose:          "⚙️ Progreso de herramientas: **VERBOSE** — cada llamada a herramienta con sus argumentos completos."
      +    saved_suffix:          "_(guardado para **{platform}** — se aplica en el próximo mensaje)_"
      +    save_failed:           "_(no se pudo guardar en la configuración: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Modo de voz activado.\nResponderé con voz cuando envíes mensajes de voz.\nUsa /voice tts para recibir respuestas de voz en todos los mensajes."
      +    disabled_text:         "Modo de voz desactivado. Respuestas solo de texto."
      +    tts_enabled:           "Auto-TTS activado.\nTodas las respuestas incluirán un mensaje de voz."
      +    status_mode:           "Modo de voz: {label}"
      +    status_channel:        "Canal de voz: #{channel}"
      +    status_participants:   "Participantes: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (hablando)"
      +    enabled_short:         "Modo de voz activado."
      +    disabled_short:        "Modo de voz desactivado."
      +    label_off:             "Desactivado (solo texto)"
      +    label_voice_only:      "Activado (responder con voz a mensajes de voz)"
      +    label_all:             "TTS (responder con voz a todos los mensajes)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Modo YOLO **DESACTIVADO** en esta sesión — los comandos peligrosos requerirán aprobación."
      +    enabled:                    "⚡ Modo YOLO **ACTIVADO** en esta sesión — todos los comandos se aprueban automáticamente. Úsalo con precaución."
      +
      +  shared:
      +    session_db_unavailable:      "Base de datos de sesiones no disponible."
      +    session_db_unavailable_prefix: "Base de datos de sesiones no disponible"
      +    session_not_found:           "Sesión no encontrada en la base de datos."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/fr.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "Aucun objectif actif."
         config_read_failed: "⚠️ Impossible de lire config.yaml : {error}"
         config_save_failed: "⚠️ Impossible de sauvegarder la configuration : {error}"
      +
      +  model:
      +    error_prefix:           "Erreur : {error}"
      +    switched:               "Modèle changé pour `{model}`"
      +    provider_label:         "Fournisseur : {provider}"
      +    context_label:          "Contexte : {tokens} tokens"
      +    max_output_label:       "Sortie max. : {tokens} tokens"
      +    cost_label:             "Coût : {cost}"
      +    capabilities_label:     "Capacités : {capabilities}"
      +    prompt_caching_enabled: "Cache de prompts : activé"
      +    warning_prefix:         "Avertissement : {warning}"
      +    saved_global:           "Enregistré dans config.yaml (`--global`)"
      +    session_only_hint:      "_(session uniquement — ajoutez `--global` pour conserver)_"
      +    current_label:          "Actuel : `{model}` chez {provider}"
      +    current_tag:            " (actuel)"
      +    more_models_suffix:     " (+{count} autres)"
      +    usage_switch_model:     "`/model <name>` — changer de modèle"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — changer de fournisseur"
      +    usage_persist:          "`/model <name> --global` — conserver"
      +
      +  agents:
      +    header:                "🤖 **Agents et tâches actifs**"
      +    active_agents:         "**Agents actifs :** {count}"
      +    this_chat:             " · ce chat"
      +    more:                  "... et {count} de plus"
      +    running_processes:     "**Processus d'arrière-plan en cours :** {count}"
      +    async_jobs:            "**Tâches asynchrones du gateway :** {count}"
      +    none:                  "Aucun agent actif ni tâche en cours."
      +    state_starting:        "démarrage"
      +    state_running:         "en cours"
      +
      +  approve:
      +    no_pending:            "Aucune commande en attente d'approbation."
      +    once_singular:         "✅ Commande approuvée. L'agent reprend..."
      +    once_plural:           "✅ Commandes approuvées ({count} commandes). L'agent reprend..."
      +    session_singular:      "✅ Commande approuvée (modèle approuvé pour cette session). L'agent reprend..."
      +    session_plural:        "✅ Commandes approuvées (modèle approuvé pour cette session) ({count} commandes). L'agent reprend..."
      +    always_singular:       "✅ Commande approuvée (modèle approuvé de manière permanente). L'agent reprend..."
      +    always_plural:         "✅ Commandes approuvées (modèle approuvé de manière permanente) ({count} commandes). L'agent reprend..."
      +
      +  background:
      +    usage:                 "Usage : /background <prompt>\nExemple : /background Résume les meilleures histoires HN d'aujourd'hui\n\nExécute le prompt dans une session séparée. Vous pouvez continuer à discuter — le résultat apparaîtra ici une fois terminé."
      +    started:               "🔄 Tâche d'arrière-plan démarrée : « {preview} »\nID de tâche : {task_id}\nVous pouvez continuer à discuter — les résultats apparaîtront ici une fois terminés."
      +
      +  branch:
      +    db_unavailable:        "Base de données des sessions indisponible."
      +    no_conversation:       "Aucune conversation à brancher — envoyez d'abord un message."
      +    create_failed:         "Échec de la création de la branche : {error}"
      +    switch_failed:         "Branche créée mais impossible de basculer dessus."
      +    branched_one:          "⑂ Branche **{title}** créée ({count} message copié)\nOriginal : `{parent}`\nBranche : `{new}`\nUtilisez `/resume` pour revenir à l'original."
      +    branched_many:         "⑂ Branche **{title}** créée ({count} messages copiés)\nOriginal : `{parent}`\nBranche : `{new}`\nUtilisez `/resume` pour revenir à l'original."
      +
      +  commands:
      +    usage:                 "Utilisation : `/commands [page]`"
      +    skill_header:          "⚡ **Commandes de skill** :"
      +    default_desc:          "Commande de skill"
      +    none:                  "Aucune commande disponible."
      +    header:                "📚 **Commandes** ({total} au total, page {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← précédent"
      +    nav_next:              "suivant → `/commands {page}`"
      +    out_of_range:          "_(La page demandée {requested} était hors limites, affichage de la page {page}.)_"
      +
      +  compress:
      +    not_enough:            "Conversation insuffisante pour la compression (au moins 4 messages nécessaires)."
      +    no_provider:           "Aucun fournisseur configuré — compression impossible."
      +    nothing_to_do:         "Rien à compresser pour l'instant (la transcription est encore entièrement du contexte protégé)."
      +    focus_line:            "Focus : \"{topic}\""
      +    summary_failed:        "⚠️ Échec de la génération du résumé ({error}). {count} message(s) historique(s) ont été supprimés et remplacés par un espace réservé ; le contexte antérieur n'est plus récupérable. Vérifiez la configuration du modèle auxiliary.compression."
      +    aux_failed:            "ℹ️ Le modèle de compression configuré `{model}` a échoué ({error}). Récupéré avec votre modèle principal — le contexte est intact — mais vous pouvez vérifier `auxiliary.compression.model` dans config.yaml."
      +    failed:                "Échec de la compression : {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Échec de l'envoi du rapport de débogage : {error}"
      +    header:                "**Rapport de débogage envoyé :**"
      +    auto_delete:           "⏱ Les pastes s'effaceront automatiquement dans 6 heures."
      +    full_logs_hint:        "Pour envoyer les journaux complets, utilisez `hermes debug share` depuis la CLI."
      +    share_hint:            "Partagez ces liens avec l'équipe Hermes pour obtenir de l'aide."
      +
      +  deny:
      +    stale:                 "❌ Commande refusée (l'approbation était périmée)."
      +    no_pending:            "Aucune commande en attente de refus."
      +    denied_singular:       "❌ Commande refusée."
      +    denied_plural:         "❌ Commandes refusées ({count} commandes)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast n'est disponible que pour les modèles OpenAI qui prennent en charge Priority Processing."
      +    status:                "⚡ Priority Processing\n\nMode actuel : `{mode}`\n\n_Usage :_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Argument inconnu : `{arg}`\n\n**Options valides :** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing : **{label}** (enregistré dans la configuration)\n_(prend effet au prochain message)_"
      +    session_only:          "⚡ ✓ Priority Processing : **{label}** (cette session uniquement)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Pied de page d'exécution : **{state}**\nChamps : `{fields}`\nPlateforme : `{platform}`"
      +    usage:                 "Usage : `/footer [on|off|status]`"
      +    saved:                 "📎 Pied de page d'exécution : **{state}**{example}\n_(enregistré globalement — prend effet au prochain message)_"
      +    example_line:          "\nExemple : `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Les objectifs ne sont pas disponibles dans cette session."
      +    no_goal_set:           "Aucun objectif défini."
      +    paused:                "⏸ Objectif en pause : {goal}"
      +    no_resume:             "Aucun objectif à reprendre."
      +    resumed:               "▶ Objectif repris : {goal}\nEnvoyez un message pour continuer, ou attendez — je passerai à l'étape suivante au prochain tour."
      +    invalid:               "Objectif invalide : {error}"
      +    set:                   "⊙ Objectif défini (budget de {budget} tours) : {goal}\nJe continuerai jusqu'à ce que l'objectif soit terminé, que vous le mettiez en pause/effaciez, ou que le budget soit épuisé.\nContrôles : /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Commandes Hermes**\n"
      +    skill_header:          "\n⚡ **Commandes de skill** ({count} actives) :"
      +    more_use_commands:     "\n... et {count} de plus. Utilisez `/commands` pour la liste paginée complète."
      +
      +  insights:
      +    invalid_days:          "Valeur --days invalide : {value}"
      +    error:                 "Erreur lors de la génération des analyses : {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ erreur kanban : {error}"
      +    subscribed_suffix:     "(abonné — vous serez notifié lorsque {task_id} se terminera ou sera bloqué)"
      +    truncated_suffix:      "… (tronqué ; utilisez `hermes kanban …` dans votre terminal pour la sortie complète)"
      +    no_output:             "(aucune sortie)"
      +
      +  personality:
      +    none_configured:       "Aucune personnalité configurée dans `{path}/config.yaml`"
      +    header:                "🎭 **Personnalités disponibles**\n"
      +    none_option:           "• `none` — (aucune superposition de personnalité)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nUtilisation : `/personality <name>`"
      +    save_failed:           "⚠️ Échec de l'enregistrement du changement de personnalité : {error}"
      +    cleared:               "🎭 Personnalité effacée — comportement de base de l'agent utilisé.\n_(prend effet au prochain message)_"
      +    set_to:                "🎭 Personnalité définie sur **{name}**\n_(prend effet au prochain message)_"
      +    unknown:               "Personnalité inconnue : `{name}`\n\nDisponibles : {available}"
      +
      +  profile:
      +    header:                "👤 **Profil :** `{profile}`"
      +    home:                  "📂 **Dossier personnel :** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (par défaut)"
      +    level_disabled:            "none (désactivé)"
      +    scope_session:             "remplacement de session"
      +    scope_global:              "configuration globale"
      +    status:                    "🧠 **Paramètres de raisonnement**\n\n**Effort :** `{level}`\n**Portée :** {scope}\n**Affichage :** {display}\n\n_Usage :_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "activé ✓"
      +    display_off:               "désactivé"
      +    display_set_on:            "🧠 ✓ Affichage du raisonnement : **ACTIVÉ**\nLa réflexion du modèle sera affichée avant chaque réponse sur **{platform}**."
      +    display_set_off:           "🧠 ✓ Affichage du raisonnement : **DÉSACTIVÉ** pour **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` n'est pas pris en charge. Utilisez `/reasoning <level> --global` pour modifier la valeur globale par défaut."
      +    reset_done:                "🧠 ✓ Remplacement de raisonnement de la session effacé ; retour à la configuration globale."
      +    unknown_arg:               "⚠️ Argument inconnu : `{arg}`\n\n**Niveaux valides :** none, minimal, low, medium, high, xhigh\n**Affichage :** show, hide\n**Persister :** ajoutez `--global` pour enregistrer au-delà de cette session"
      +    set_global:                "🧠 ✓ Effort de raisonnement défini sur `{effort}` (enregistré dans la configuration)\n_(prend effet au prochain message)_"
      +    set_global_save_failed:    "🧠 ✓ Effort de raisonnement défini sur `{effort}` (session uniquement — échec de l'enregistrement de la configuration)\n_(prend effet au prochain message)_"
      +    set_session:               "🧠 ✓ Effort de raisonnement défini sur `{effort}` (session uniquement — ajoutez `--global` pour persister)\n_(prend effet au prochain message)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp annulé. Outils MCP inchangés."
      +    always_followup:       "ℹ️ Les prochains appels `/reload-mcp` s'exécuteront sans confirmation. Réactivez via `approvals.mcp_reload_confirm: true` dans `config.yaml`."
      +    confirm_prompt:        "⚠️ **Confirmer /reload-mcp**\n\nRecharger les serveurs MCP reconstruit l'ensemble d'outils de cette session et **invalide le cache de prompt du fournisseur** — le prochain message renverra l'intégralité des jetons d'entrée. Sur les modèles à long contexte ou à raisonnement élevé, cela peut être coûteux.\n\nChoisissez :\n• **Approuver une fois** — recharger maintenant\n• **Toujours approuver** — recharger maintenant et masquer cette confirmation définitivement\n• **Annuler** — laisser les outils MCP inchangés\n\n_Alternative texte : répondez `/approve`, `/always` ou `/cancel`._"
      +    header:                "🔄 **Serveurs MCP rechargés**\n"
      +    reconnected:           "♻️ Reconnectés : {names}"
      +    added:                 "➕ Ajoutés : {names}"
      +    removed:               "➖ Supprimés : {names}"
      +    none_connected:        "Aucun serveur MCP connecté."
      +    tools_available:       "\n🔧 {tools} outil(s) disponible(s) sur {servers} serveur(s)"
      +    failed:                "❌ Échec du rechargement MCP : {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skills rechargées**\n"
      +    no_new:                "Aucune nouvelle skill détectée."
      +    total:                 "\n📚 {count} skill(s) disponible(s)"
      +    added_header:          "➕ **Skills ajoutées :**"
      +    removed_header:        "➖ **Skills supprimées :**"
      +    item_with_desc:        "    - {name} : {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Échec du rechargement des skills : {error}"
      +
      +  reset:
      +    header_default:        "✨ Session réinitialisée ! Nouveau départ."
      +    header_new:            "✨ Nouvelle session démarrée !"
      +    header_titled:         "✨ Nouvelle session démarrée : {title}"
      +    title_rejected:        "\n⚠️ Titre refusé : {error}"
      +    title_error_untitled:  "\n⚠️ {error} — session démarrée sans titre."
      +    title_empty_untitled:  "\n⚠️ Le titre est vide après nettoyage — session démarrée sans titre."
      +    tip:                   "\n✦ Astuce : {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Redémarrage du gateway déjà en cours..."
      +    restarting:            "♻ Redémarrage du gateway. Si vous n'êtes pas notifié dans les 60 secondes, redémarrez depuis la console avec `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Base de données des sessions indisponible."
      +    no_named_sessions:     "Aucune session nommée trouvée.\nUtilisez `/title Ma session` pour nommer la session actuelle, puis `/resume Ma session` pour y revenir plus tard."
      +    list_header:           "📋 **Sessions nommées**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nUsage : `/resume <nom de session>`"
      +    list_failed:           "Impossible de lister les sessions : {error}"
      +    not_found:             "Aucune session correspondant à '**{name}**' trouvée.\nUtilisez `/resume` sans argument pour voir les sessions disponibles."
      +    already_on:            "📌 Déjà sur la session **{name}**."
      +    switch_failed:         "Échec du changement de session."
      +    resumed_one:           "↻ Session **{title}** reprise ({count} message). Conversation restaurée."
      +    resumed_many:          "↻ Session **{title}** reprise ({count} messages). Conversation restaurée."
      +    resumed_no_count:      "↻ Session **{title}** reprise. Conversation restaurée."
      +
      +  retry:
      +    no_previous:           "Aucun message précédent à réessayer."
      +
      +  rollback:
      +    not_enabled:           "Les points de contrôle ne sont pas activés.\nActivez-les dans config.yaml :\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Aucun point de contrôle trouvé pour {cwd}"
      +    invalid_number:        "Numéro de point de contrôle invalide. Utilisez 1-{max}."
      +    restored:              "✅ Restauré au point de contrôle {hash} : {reason}\nUn instantané pré-rollback a été enregistré automatiquement."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Impossible d'enregistrer le canal principal : {error}"
      +    success:               "✅ Canal principal défini sur **{name}** (ID : {chat_id}).\nLes tâches cron et les messages multi-plateformes seront livrés ici."
      +
      +  status:
      +    header:                "📊 **État de Hermes Gateway**"
      +    session_id:            "**ID de session :** `{session_id}`"
      +    title:                 "**Titre :** {title}"
      +    created:               "**Créé :** {timestamp}"
      +    last_activity:         "**Dernière activité :** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] :** {tokens}"
      +    agent_running:         "**Agent en cours :** {state}"
      +    state_yes:             "Oui ⚡"
      +    state_no:              "Non"
      +    queued:                "**Suivis en file :** {count}"
      +    platforms:             "**Plateformes connectées :** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Arrêté. L'agent n'avait pas encore commencé — vous pouvez continuer cette session."
      +    stopped:               "⚡ Arrêté. Vous pouvez continuer cette session."
      +    no_active:             "Aucune tâche active à arrêter."
      +
      +  title:
      +    db_unavailable:        "Base de données des sessions indisponible."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Le titre est vide après nettoyage. Utilisez des caractères imprimables."
      +    set_to:                "✏️ Titre de session défini : **{title}**"
      +    not_found:             "Session introuvable dans la base de données."
      +    current_with_title:    "📌 Session : `{session_id}`\nTitre : **{title}**"
      +    current_no_title:      "📌 Session : `{session_id}`\nAucun titre défini. Usage : `/title Mon nom de session`"
      +
      +  topic:
      +    not_telegram_dm:         "La commande /topic n'est disponible que dans les chats privés Telegram."
      +    no_session_db:           "Base de données de sessions non disponible."
      +    unauthorized:            "Vous n'êtes pas autorisé à utiliser /topic sur ce bot."
      +    restore_needs_topic:     "Pour restaurer une session, créez ou ouvrez d'abord un topic Telegram, puis envoyez /topic <session-id> dans ce topic. Pour créer un nouveau topic, ouvrez All Messages et envoyez-y n'importe quel message."
      +    topics_disabled:         "Les topics Telegram ne sont pas encore activés pour ce bot.\n\nComment les activer :\n1. Ouvrez @BotFather.\n2. Choisissez votre bot.\n3. Ouvrez Bot Settings → Threads Settings.\n4. Activez Threaded Mode et assurez-vous que les utilisateurs sont autorisés à créer de nouveaux threads.\n\nPuis envoyez /topic à nouveau."
      +    topics_user_disallowed:  "Les topics Telegram sont activés, mais les utilisateurs ne peuvent pas en créer.\n\nOuvrez @BotFather → choisissez votre bot → Bot Settings → Threads Settings, puis désactivez 'Disallow users to create new threads'.\n\nPuis envoyez /topic à nouveau."
      +    enable_failed:           "Échec de l'activation du mode topic Telegram : {error}"
      +    bound_status:            "Ce topic est lié à :\nSession : {label}\nID : {session_id}\n\nUtilisez /new pour remplacer ce topic par une nouvelle session.\nPour un travail parallèle, ouvrez All Messages et envoyez-y un message pour créer un autre topic."
      +    thread_ready:            "Les topics multi-sessions Telegram sont activés.\n\nCe topic sera utilisé comme session Hermes indépendante. Utilisez /new pour remplacer la session actuelle de ce topic. Pour un travail parallèle, ouvrez All Messages et envoyez-y un message pour créer un autre topic."
      +    untitled_session:        "Session sans titre"
      +
      +  undo:
      +    nothing:               "Rien à annuler."
      +    removed:               "↩️ {count} message(s) annulé(s).\nSupprimé : « {preview} »"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update n'est disponible que depuis les plateformes de messagerie. Exécutez `hermes update` depuis le terminal."
      +    not_git_repo:            "✗ Pas un dépôt git — impossible de mettre à jour."
      +    hermes_cmd_not_found:    "✗ Impossible de localiser la commande `hermes`. Hermes est en cours d'exécution, mais la commande de mise à jour n'a pas pu trouver l'exécutable dans le PATH ni via l'interpréteur Python actuel. Essayez d'exécuter `hermes update` manuellement dans votre terminal."
      +    start_failed:            "✗ Échec du démarrage de la mise à jour : {error}"
      +    starting:                "⚕ Démarrage de la mise à jour Hermes… Je diffuserai la progression ici."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Limites de débit :** {state}"
      +    header_session:           "📊 **Utilisation des jetons de session**"
      +    label_model:              "Modèle : `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] d'entrée : {count}"
      +    label_cache_read:         "Jetons de lecture du cache : {count}"
      +    label_cache_write:        "Jetons d'écriture du cache : {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] de sortie : {count}"
      +    label_total:              "Total : {count}"
      +    label_api_calls:          "Appels API : {count}"
      +    label_cost:               "Coût : {prefix}${amount}"
      +    label_cost_included:      "Coût : inclus"
      +    label_context:            "Contexte : {used} / {total} ({pct}%)"
      +    label_compressions:       "Compressions : {count}"
      +    header_session_info:      "📊 **Infos de session**"
      +    label_messages:           "Messages : {count}"
      +    label_estimated_context:  "Contexte estimé : ~{count} jetons"
      +    detailed_after_first:     "_(Utilisation détaillée disponible après la première réponse de l'agent)_"
      +    no_data:                  "Aucune donnée d'utilisation disponible pour cette session."
      +
      +  verbose:
      +    not_enabled:           "La commande `/verbose` n'est pas activée pour les plateformes de messagerie.\n\nActivez-la dans `config.yaml` :\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Progression des outils : **OFF** — aucune activité d'outil affichée."
      +    mode_new:              "⚙️ Progression des outils : **NEW** — affichée lors d'un changement d'outil (longueur d'aperçu : `display.tool_preview_length`, par défaut 40)."
      +    mode_all:              "⚙️ Progression des outils : **ALL** — chaque appel d'outil est affiché (longueur d'aperçu : `display.tool_preview_length`, par défaut 40)."
      +    mode_verbose:          "⚙️ Progression des outils : **VERBOSE** — chaque appel d'outil avec ses arguments complets."
      +    saved_suffix:          "_(enregistré pour **{platform}** — prend effet au prochain message)_"
      +    save_failed:           "_(impossible d'enregistrer dans la configuration : {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Mode vocal activé.\nJe répondrai en vocal quand vous envoyez des messages vocaux.\nUtilisez /voice tts pour obtenir des réponses vocales à tous les messages."
      +    disabled_text:         "Mode vocal désactivé. Réponses uniquement textuelles."
      +    tts_enabled:           "TTS automatique activé.\nToutes les réponses incluront un message vocal."
      +    status_mode:           "Mode vocal : {label}"
      +    status_channel:        "Canal vocal : #{channel}"
      +    status_participants:   "Participants : {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (parle)"
      +    enabled_short:         "Mode vocal activé."
      +    disabled_short:        "Mode vocal désactivé."
      +    label_off:             "Désactivé (texte seulement)"
      +    label_voice_only:      "Activé (réponse vocale aux messages vocaux)"
      +    label_all:             "TTS (réponse vocale à tous les messages)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Mode YOLO **DÉSACTIVÉ** pour cette session — les commandes dangereuses nécessiteront une approbation."
      +    enabled:                    "⚡ Mode YOLO **ACTIVÉ** pour cette session — toutes les commandes sont auto-approuvées. À utiliser avec prudence."
      +
      +  shared:
      +    session_db_unavailable:      "Base de données de sessions indisponible."
      +    session_db_unavailable_prefix: "Base de données de sessions indisponible"
      +    session_not_found:           "Session introuvable dans la base de données."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/ga.yaml
      @@ -0,0 +1,354 @@
      +# Hermes static-message catalog -- Gaeilge (Irish)
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +#
      +# Modern Irish technical writing freely uses English loanwords for terms
      +# without good native equivalents (e.g. "session", "tokens", "API").
      +# Where Irish has a settled term we use it; otherwise we keep the English.
      +
      +approval:
      +  dangerous_header: "⚠️  ORDÚ CONTÚIRTEACH: {description}"
      +  choose_long:     "      [o]uair amháin  |  [s]eisiún  |  [a]i gcónaí  |  [d]iúltaigh"
      +  choose_short:    "      [o]uair amháin  |  [s]eisiún  |  [d]iúltaigh"
      +  prompt_long:     "      Rogha [o/s/a/D]: "
      +  prompt_short:    "      Rogha [o/s/D]: "
      +  timeout:         "      ⏱ Am istigh — ag diúltú don ordú"
      +  allowed_once:    "      ✓ Ceadaithe uair amháin"
      +  allowed_session: "      ✓ Ceadaithe don seisiún seo"
      +  allowed_always:  "      ✓ Curtha leis an liosta ceadaithe buan"
      +  denied:          "      ✗ Diúltaithe"
      +  cancelled:       "      ✗ Cealaithe"
      +  blocklist_message: "Tá an t-ordú seo ar an liosta cosc gan choinníoll agus ní féidir é a cheadú."
      +
      +gateway:
      +  approval_expired: "⚠️ Tá an cead imithe in éag (níl an gníomhaire ag fanacht níos mó). Iarr ar an ngníomhaire iarracht eile a dhéanamh."
      +  draining:         "⏳ Ag fanacht le {count} gníomhaire(í) gníomhach roimh atosú..."
      +  goal_cleared:     "✓ Sprioc glanta."
      +  no_active_goal:   "Níl aon sprioc ghníomhach ann."
      +  config_read_failed: "⚠️ Níorbh fhéidir config.yaml a léamh: {error}"
      +  config_save_failed: "⚠️ Níorbh fhéidir an chumraíocht a shábháil: {error}"
      +
      +  model:
      +    error_prefix:           "Earráid: {error}"
      +    switched:               "Athraíodh an tsamhail go `{model}`"
      +    provider_label:         "Soláthraí: {provider}"
      +    context_label:          "Comhthéacs: {tokens} comhartha"
      +    max_output_label:       "Aschur uasta: {tokens} comhartha"
      +    cost_label:             "Costas: {cost}"
      +    capabilities_label:     "Cumais: {capabilities}"
      +    prompt_caching_enabled: "Taisceadh leid: cumasaithe"
      +    warning_prefix:         "Rabhadh: {warning}"
      +    saved_global:           "Sábháilte i config.yaml (`--global`)"
      +    session_only_hint:      "_(seisiún amháin — cuir `--global` leis chun é a choinneáil)_"
      +    current_label:          "Reatha: `{model}` ar {provider}"
      +    current_tag:            " (reatha)"
      +    more_models_suffix:     " (+{count} eile)"
      +    usage_switch_model:     "`/model <name>` — athraigh an tsamhail"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — athraigh an soláthraí"
      +    usage_persist:          "`/model <name> --global` — coinnigh"
      +
      +  agents:
      +    header:                "🤖 **Gníomhairí & Tascanna Gníomhacha**"
      +    active_agents:         "**Gníomhairí gníomhacha:** {count}"
      +    this_chat:             " · an comhrá seo"
      +    more:                  "... agus {count} eile"
      +    running_processes:     "**Próisis chúlra ag rith:** {count}"
      +    async_jobs:            "**Tascanna asincrónacha gateway:** {count}"
      +    none:                  "Níl aon ghníomhairí gníomhacha ná tascanna ag rith."
      +    state_starting:        "ag tosú"
      +    state_running:         "ag rith"
      +
      +  approve:
      +    no_pending:            "Níl aon ordú ag fanacht le ceadú."
      +    once_singular:         "✅ Ordú ceadaithe. Tá an gníomhaire ag atosú..."
      +    once_plural:           "✅ Orduithe ceadaithe ({count} ordú). Tá an gníomhaire ag atosú..."
      +    session_singular:      "✅ Ordú ceadaithe (patrún ceadaithe don seisiún seo). Tá an gníomhaire ag atosú..."
      +    session_plural:        "✅ Orduithe ceadaithe (patrún ceadaithe don seisiún seo) ({count} ordú). Tá an gníomhaire ag atosú..."
      +    always_singular:       "✅ Ordú ceadaithe (patrún ceadaithe go buan). Tá an gníomhaire ag atosú..."
      +    always_plural:         "✅ Orduithe ceadaithe (patrún ceadaithe go buan) ({count} ordú). Tá an gníomhaire ag atosú..."
      +
      +  background:
      +    usage:                 "Úsáid: /background <leid>\nSampla: /background Déan achoimre ar phríomhscéalta HN inniu\n\nRitheann an leid i seisiún ar leith. Is féidir leat leanúint leis an gcomhrá — taispeánfar an toradh anseo nuair a bheidh sé críochnaithe."
      +    started:               "🔄 Tasc cúlra tosaithe: \"{preview}\"\nAitheantas an tasc: {task_id}\nIs féidir leat leanúint leis an gcomhrá — taispeánfar na torthaí nuair a bheidh sé críochnaithe."
      +
      +  branch:
      +    db_unavailable:        "Níl bunachar sonraí na seisiún ar fáil."
      +    no_conversation:       "Níl aon chomhrá le brainseáil — seol teachtaireacht ar dtús."
      +    create_failed:         "Theip ar an mbrainse a chruthú: {error}"
      +    switch_failed:         "Cruthaíodh an brainse ach theip ar athrú chuige."
      +    branched_one:          "⑂ Brainseáilte go **{title}** ({count} teachtaireacht cóipeáilte)\nBunaidh: `{parent}`\nBrainse: `{new}`\nÚsáid `/resume` chun filleadh ar an mbunaidh."
      +    branched_many:         "⑂ Brainseáilte go **{title}** ({count} teachtaireacht cóipeáilte)\nBunaidh: `{parent}`\nBrainse: `{new}`\nÚsáid `/resume` chun filleadh ar an mbunaidh."
      +
      +  commands:
      +    usage:                 "Úsáid: `/commands [page]`"
      +    skill_header:          "⚡ **Orduithe Scileanna**:"
      +    default_desc:          "Ordú scile"
      +    none:                  "Níl aon ordú ar fáil."
      +    header:                "📚 **Orduithe** ({total} san iomlán, leathanach {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← roimhe seo"
      +    nav_next:              "ar aghaidh → `/commands {page}`"
      +    out_of_range:          "_(Bhí leathanach {requested} a iarradh as raon, ag taispeáint leathanach {page}.)_"
      +
      +  compress:
      +    not_enough:            "Níl go leor comhrá le dlúthú (teastaíonn 4 theachtaireacht ar a laghad)."
      +    no_provider:           "Níl aon soláthraí cumraithe — ní féidir dlúthú."
      +    nothing_to_do:         "Níl aon rud le dlúthú fós (tá an traschríbhinn fós uile mar chomhthéacs cosanta)."
      +    focus_line:            "Fócas: \"{topic}\""
      +    summary_failed:        "⚠️ Theip ar ghiniúint achoimre ({error}). Baineadh {count} teachtaireacht stairiúil agus cuireadh ionadaí ina n-áit; níl an comhthéacs roimhe seo in-aisghabhála a thuilleadh. Smaoinigh ar an gcumraíocht auxiliary.compression a sheiceáil."
      +    aux_failed:            "ℹ️ Theip ar an tsamhail dlúthúcháin chumraithe `{model}` ({error}). Aisghafa ag baint úsáide as do phríomhshamhail — tá an comhthéacs slán — ach b'fhéidir gur mhaith leat `auxiliary.compression.model` i config.yaml a sheiceáil."
      +    failed:                "Theip ar dhlúthú: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Theip ar uaslódáil tuairisce dífhabhtaithe: {error}"
      +    header:                "**Tuairisc dhífhabhtaithe uaslódáilte:**"
      +    auto_delete:           "⏱ Scriosfar na pastes go huathoibríoch i 6 huaire."
      +    full_logs_hint:        "Le haghaidh uaslódálacha logála iomlána, úsáid `hermes debug share` ón CLI."
      +    share_hint:            "Roinn na naisc seo le foireann Hermes le haghaidh tacaíochta."
      +
      +  deny:
      +    stale:                 "❌ Ordú diúltaithe (bhí an cead imithe i léig)."
      +    no_pending:            "Níl aon ordú ag fanacht le diúltú."
      +    denied_singular:       "❌ Ordú diúltaithe."
      +    denied_plural:         "❌ Orduithe diúltaithe ({count} ordú)."
      +
      +  fast:
      +    not_supported:         "⚡ Tá /fast ar fáil amháin do shamhlacha OpenAI a thacaíonn le Priority Processing."
      +    status:                "⚡ Priority Processing\n\nMód reatha: `{mode}`\n\n_Úsáid:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Argóint anaithnid: `{arg}`\n\n**Roghanna bailí:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (sábháilte sa chumraíocht)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (an seisiún seo amháin)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Buntásc rite: **{state}**\nRéimsí: `{fields}`\nArdán: `{platform}`"
      +    usage:                 "Úsáid: `/footer [on|off|status]`"
      +    saved:                 "📎 Buntásc rite: **{state}**{example}\n_(sábháilte go domhanda — éifeachtach ón gcéad teachtaireacht eile)_"
      +    example_line:          "\nSampla: `{preview}`"
      +    state_on:              "AR"
      +    state_off:             "AS"
      +
      +  goal:
      +    unavailable:           "Níl spriocanna ar fáil sa seisiún seo."
      +    no_goal_set:           "Níl aon sprioc socraithe."
      +    paused:                "⏸ Sprioc curtha ar sos: {goal}"
      +    no_resume:             "Níl aon sprioc le hatosú."
      +    resumed:               "▶ Sprioc atosaithe: {goal}\nSeol teachtaireacht ar bith chun leanúint, nó fan — déanfaidh mé an chéad chéim eile sa chéad seal eile."
      +    invalid:               "Sprioc neamhbhailí: {error}"
      +    set:                   "⊙ Sprioc socraithe (buiséad {budget} seal): {goal}\nLeanfaidh mé ag obair go dtí go bhfuil an sprioc críochnaithe, go gcuirfidh tú ar sos / go nglanfaidh tú í, nó go n-úsáidfear an buiséad.\nSmacht: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Orduithe Hermes**\n"
      +    skill_header:          "\n⚡ **Orduithe Scileanna** ({count} gníomhach):"
      +    more_use_commands:     "\n... agus {count} eile. Úsáid `/commands` don liosta iomlán uimhrithe."
      +
      +  insights:
      +    invalid_days:          "Luach --days neamhbhailí: {value}"
      +    error:                 "Earráid agus léargais á gcruthú: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ earráid kanban: {error}"
      +    subscribed_suffix:     "(síntiúsaithe — cuirfear in iúl duit nuair a chríochnóidh nó a stopfaidh {task_id})"
      +    truncated_suffix:      "… (giorraithe; úsáid `hermes kanban …` i do theirminéal le haghaidh aschur iomláin)"
      +    no_output:             "(gan aschur)"
      +
      +  personality:
      +    none_configured:       "Níl aon phearsantachtaí cumraithe in `{path}/config.yaml`"
      +    header:                "🎭 **Pearsantachtaí ar fáil**\n"
      +    none_option:           "• `none` — (gan forleagan pearsantachta)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nÚsáid: `/personality <name>`"
      +    save_failed:           "⚠️ Theip ar shábháil athraithe pearsantachta: {error}"
      +    cleared:               "🎭 Pearsantacht glanta — ag úsáid iompair bunúsaigh an ghníomhaire.\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +    set_to:                "🎭 Pearsantacht socraithe go **{name}**\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +    unknown:               "Pearsantacht anaithnid: `{name}`\n\nAr fáil: {available}"
      +
      +  profile:
      +    header:                "👤 **Próifíl:** `{profile}`"
      +    home:                  "📂 **Baile:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (réamhshocraithe)"
      +    level_disabled:            "none (díchumasaithe)"
      +    scope_session:             "sárú seisiúin"
      +    scope_global:              "cumraíocht dhomhanda"
      +    status:                    "🧠 **Socruithe Réasúnaíochta**\n\n**Iarracht:** `{level}`\n**Scóip:** {scope}\n**Taispeáint:** {display}\n\n_Úsáid:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "ar ✓"
      +    display_off:               "as"
      +    display_set_on:            "🧠 ✓ Taispeáint réasúnaíochta: **AR**\nTaispeánfar smaointeoireacht na samhla roimh gach freagra ar **{platform}**."
      +    display_set_off:           "🧠 ✓ Taispeáint réasúnaíochta: **AS** do **{platform}**"
      +    reset_global_unsupported:  "⚠️ Ní thacaítear le `/reasoning reset --global`. Úsáid `/reasoning <level> --global` chun an réamhshocrú domhanda a athrú."
      +    reset_done:                "🧠 ✓ Sárú réasúnaíochta seisiúin glanta; ag titim siar ar an gcumraíocht dhomhanda."
      +    unknown_arg:               "⚠️ Argóint anaithnid: `{arg}`\n\n**Leibhéil bhailí:** none, minimal, low, medium, high, xhigh\n**Taispeáint:** show, hide\n**Coinnigh:** cuir `--global` leis chun sábháil thar an seisiún seo"
      +    set_global:                "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (sábháilte sa chumraíocht)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +    set_global_save_failed:    "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (seisiún amháin — theip ar shábháil cumraíochta)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +    set_session:               "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (seisiún amháin — cuir `--global` leis chun é a choinneáil)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp cealaithe. Tá uirlisí MCP gan athrú."
      +    always_followup:       "ℹ️ Rithfear glaonna `/reload-mcp` amach anseo gan dearbhú. Athchumasaigh trí `approvals.mcp_reload_confirm: true` a shocrú in config.yaml."
      +    confirm_prompt:        "⚠️ **Dearbhaigh /reload-mcp**\n\nAthlódáil freastalaithe MCP a athchruthaíonn an tacar uirlisí don seisiún seo agus **cuireann sé taisce leid an tsoláthraí ar neamhní** — seolfaidh an chéad teachtaireacht eile na comharthaí ionchuir iomlána arís. Ar shamhlacha le comhthéacs fada nó réasúnaíocht ard, is féidir leis seo a bheith costasach.\n\nRoghnaigh:\n• **Approve Once** — athlódáil anois\n• **Always Approve** — athlódáil anois agus an leid seo a chiúnú go buan\n• **Cancel** — fág uirlisí MCP gan athrú\n\n_Cúltaca téacs: freagair `/approve`, `/always`, nó `/cancel`._"
      +    header:                "🔄 **Freastalaithe MCP Athlódáilte**\n"
      +    reconnected:           "♻️ Athcheanglaithe: {names}"
      +    added:                 "➕ Curtha leis: {names}"
      +    removed:               "➖ Bainte: {names}"
      +    none_connected:        "Níl aon fhreastalaí MCP ceangailte."
      +    tools_available:       "\n🔧 {tools} uirlis(í) ar fáil ó {servers} freastalaí(thí)"
      +    failed:                "❌ Theip ar athlódáil MCP: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Scileanna Athlódáilte**\n"
      +    no_new:                "Níor braitheadh aon scil nua."
      +    total:                 "\n📚 {count} scil(eanna) ar fáil"
      +    added_header:          "➕ **Scileanna Curtha leis:**"
      +    removed_header:        "➖ **Scileanna Bainte:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Theip ar athlódáil scileanna: {error}"
      +
      +  reset:
      +    header_default:        "✨ Seisiún athshocraithe! Ag tosú as an nua."
      +    header_new:            "✨ Seisiún nua tosaithe!"
      +    header_titled:         "✨ Seisiún nua tosaithe: {title}"
      +    title_rejected:        "\n⚠️ Teideal diúltaithe: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — seisiún tosaithe gan teideal."
      +    title_empty_untitled:  "\n⚠️ Tá an teideal folamh tar éis glanta — seisiún tosaithe gan teideal."
      +    tip:                   "\n✦ Leid: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Tá atosú gateway ar siúl cheana féin..."
      +    restarting:            "♻ Ag atosú gateway. Mura gcuirfear in iúl duit laistigh de 60 soicind, atosaigh ón gconsól le `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Níl bunachar sonraí na seisiún ar fáil."
      +    no_named_sessions:     "Níor aimsíodh aon seisiún ainmnithe.\nÚsáid `/title M'Ainm Seisiúin` chun do sheisiún reatha a ainmniú, ansin `/resume M'Ainm Seisiúin` chun filleadh air níos déanaí."
      +    list_header:           "📋 **Seisiúin Ainmnithe**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nÚsáid: `/resume <session name>`"
      +    list_failed:           "Níorbh fhéidir seisiúin a liostáil: {error}"
      +    not_found:             "Níor aimsíodh aon seisiún ag teacht le '**{name}**'.\nÚsáid `/resume` gan argóintí chun seisiúin atá ar fáil a fheiceáil."
      +    already_on:            "📌 Cheana ar an seisiún **{name}**."
      +    switch_failed:         "Theip ar athrú seisiúin."
      +    resumed_one:           "↻ Seisiún **{title}** atosaithe ({count} teachtaireacht). Comhrá aischurtha."
      +    resumed_many:          "↻ Seisiún **{title}** atosaithe ({count} teachtaireacht). Comhrá aischurtha."
      +    resumed_no_count:      "↻ Seisiún **{title}** atosaithe. Comhrá aischurtha."
      +
      +  retry:
      +    no_previous:           "Níl aon teachtaireacht roimhe seo le hath-iarraidh."
      +
      +  rollback:
      +    not_enabled:           "Níl seicphointí cumasaithe.\nCumasaigh in config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Níor aimsíodh aon seicphointe do {cwd}"
      +    invalid_number:        "Uimhir seicphointe neamhbhailí. Úsáid 1-{max}."
      +    restored:              "✅ Aischurtha go seicphointe {hash}: {reason}\nSábháladh roghchóip réamh-rollback go huathoibríoch."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Theip ar shábháil chainéil bhaile: {error}"
      +    success:               "✅ Cainéal baile socraithe go **{name}** (ID: {chat_id}).\nSeachadfar tascanna cron agus teachtaireachtaí trasardáin anseo."
      +
      +  status:
      +    header:                "📊 **Stádas Hermes Gateway**"
      +    session_id:            "**ID Seisiúin:** `{session_id}`"
      +    title:                 "**Teideal:** {title}"
      +    created:               "**Cruthaithe:** {timestamp}"
      +    last_activity:         "**Gníomhaíocht is déanaí:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Gníomhaire ag rith:** {state}"
      +    state_yes:             "Tá ⚡"
      +    state_no:              "Níl"
      +    queued:                "**Tascanna i scuaine:** {count}"
      +    platforms:             "**Ardáin Cheangailte:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Stoptha. Ní raibh an gníomhaire tosaithe fós — is féidir leat leanúint leis an seisiún seo."
      +    stopped:               "⚡ Stoptha. Is féidir leat leanúint leis an seisiún seo."
      +    no_active:             "Níl aon tasc gníomhach le stopadh."
      +
      +  title:
      +    db_unavailable:        "Níl bunachar sonraí na seisiún ar fáil."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Tá an teideal folamh tar éis glanta. Bain úsáid as carachtair inphriontáilte le do thoil."
      +    set_to:                "✏️ Teideal seisiúin socraithe: **{title}**"
      +    not_found:             "Seisiún gan a aimsiú sa bhunachar sonraí."
      +    current_with_title:    "📌 Seisiún: `{session_id}`\nTeideal: **{title}**"
      +    current_no_title:      "📌 Seisiún: `{session_id}`\nGan teideal socraithe. Úsáid: `/title M'Ainm Seisiúin`"
      +
      +  topic:
      +    not_telegram_dm:         "Tá an t-ordú /topic ar fáil amháin i gcomhráite príobháideacha Telegram."
      +    no_session_db:           "Níl bunachar sonraí na seisiún ar fáil."
      +    unauthorized:            "Níl tú údaraithe chun /topic a úsáid ar an mbot seo."
      +    restore_needs_topic:     "Chun seisiún a athchóiriú, cruthaigh nó oscail topaic Telegram ar dtús, ansin seol /topic <session-id> taobh istigh den topaic sin. Chun topaic nua a chruthú, oscail All Messages agus seol teachtaireacht ar bith ann."
      +    topics_disabled:         "Níl topaicí Telegram cumasaithe don bhot seo fós.\n\nConas iad a chumasú:\n1. Oscail @BotFather.\n2. Roghnaigh do bhot.\n3. Oscail Bot Settings → Threads Settings.\n4. Casadh ar Threaded Mode agus déan cinnte go bhfuil cead ag úsáideoirí snáitheanna nua a chruthú.\n\nAnsin seol /topic arís."
      +    topics_user_disallowed:  "Tá topaicí Telegram cumasaithe, ach níl cead ag úsáideoirí topaicí a chruthú.\n\nOscail @BotFather → roghnaigh do bhot → Bot Settings → Threads Settings, ansin múchadh 'Disallow users to create new threads'.\n\nAnsin seol /topic arís."
      +    enable_failed:           "Theip ar mhodh topaice Telegram a chumasú: {error}"
      +    bound_status:            "Tá an topaic seo nasctha le:\nSeisiún: {label}\nID: {session_id}\n\nÚsáid /new chun an topaic seo a athsholáthar le seisiún úr.\nLe haghaidh oibre comhthreomhaire, oscail All Messages agus seol teachtaireacht ann chun topaic eile a chruthú."
      +    thread_ready:            "Tá topaicí il-seisiúin Telegram cumasaithe.\n\nÚsáidfear an topaic seo mar sheisiún Hermes neamhspleách. Úsáid /new chun seisiún reatha na topaice seo a athsholáthar. Le haghaidh oibre comhthreomhaire, oscail All Messages agus seol teachtaireacht ann chun topaic eile a chruthú."
      +    untitled_session:        "Seisiún gan teideal"
      +
      +  undo:
      +    nothing:               "Níl aon rud le cealú."
      +    removed:               "↩️ Cealaíodh {count} teachtaireacht.\nBaineadh: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ Tá /update ar fáil amháin ó ardáin teachtaireachtaí. Rith `hermes update` ón teirminéal."
      +    not_git_repo:            "✗ Ní stór git é seo — ní féidir nuashonrú."
      +    hermes_cmd_not_found:    "✗ Níorbh fhéidir an t-ordú `hermes` a aimsiú. Tá Hermes ag rith, ach níorbh fhéidir leis an ordú nuashonraithe an inrite a aimsiú ar PATH ná tríd an léirmhínitheoir Python reatha. Bain triail as `hermes update` a rith de láimh i do theirminéal."
      +    start_failed:            "✗ Theip ar nuashonrú a thosú: {error}"
      +    starting:                "⚕ Ag tosú nuashonrú Hermes… Cuirfidh mé an dul chun cinn ar shruth anseo."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Teorainneacha Ráta:** {state}"
      +    header_session:           "📊 **Úsáid Comharthaí Seisiúin**"
      +    label_model:              "Samhail: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] ionchuir: {count}"
      +    label_cache_read:         "Comharthaí léite ón taisce: {count}"
      +    label_cache_write:        "Comharthaí scríofa sa taisce: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] aschuir: {count}"
      +    label_total:              "Iomlán: {count}"
      +    label_api_calls:          "Glaonna API: {count}"
      +    label_cost:               "Costas: {prefix}${amount}"
      +    label_cost_included:      "Costas: san áireamh"
      +    label_context:            "Comhthéacs: {used} / {total} ({pct}%)"
      +    label_compressions:       "Dlúthuithe: {count}"
      +    header_session_info:      "📊 **Eolas Seisiúin**"
      +    label_messages:           "Teachtaireachtaí: {count}"
      +    label_estimated_context:  "Comhthéacs measta: ~{count} comhartha"
      +    detailed_after_first:     "_(Úsáid mhionsonraithe ar fáil tar éis chéad fhreagra an ghníomhaire)_"
      +    no_data:                  "Níl aon sonraí úsáide ar fáil don seisiún seo."
      +
      +  verbose:
      +    not_enabled:           "Níl an t-ordú `/verbose` cumasaithe d'ardáin teachtaireachtaí.\n\nCumasaigh in `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Dul chun cinn uirlise: **AS** — gan aon ghníomhaíocht uirlise á thaispeáint."
      +    mode_new:              "⚙️ Dul chun cinn uirlise: **NUA** — taispeánta nuair a athraíonn an uirlis (fad réamhamhairc: `display.tool_preview_length`, réamhshocrú 40)."
      +    mode_all:              "⚙️ Dul chun cinn uirlise: **GACH CEANN** — taispeántar gach glao uirlise (fad réamhamhairc: `display.tool_preview_length`, réamhshocrú 40)."
      +    mode_verbose:          "⚙️ Dul chun cinn uirlise: **BÉALSCAOILTE** — gach glao uirlise le hargóintí iomlána."
      +    saved_suffix:          "_(sábháilte do **{platform}** — éifeachtach ón gcéad teachtaireacht eile)_"
      +    save_failed:           "_(níorbh fhéidir sábháil sa chumraíocht: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Mód gutha cumasaithe.\nFreagróidh mé le guth nuair a sheolann tú teachtaireachtaí gutha.\nÚsáid /voice tts chun freagraí gutha a fháil do gach teachtaireacht."
      +    disabled_text:         "Mód gutha díchumasaithe. Freagraí téacs amháin."
      +    tts_enabled:           "Auto-TTS cumasaithe.\nBeidh teachtaireacht gutha mar chuid de gach freagra."
      +    status_mode:           "Mód gutha: {label}"
      +    status_channel:        "Cainéal gutha: #{channel}"
      +    status_participants:   "Rannpháirtithe: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (ag labhairt)"
      +    enabled_short:         "Mód gutha cumasaithe."
      +    disabled_short:        "Mód gutha díchumasaithe."
      +    label_off:             "As (téacs amháin)"
      +    label_voice_only:      "Ar (freagra gutha do theachtaireachtaí gutha)"
      +    label_all:             "TTS (freagra gutha do gach teachtaireacht)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Mód YOLO **AS** don seisiún seo — beidh cead de dhíth d'orduithe contúirteacha."
      +    enabled:                    "⚡ Mód YOLO **AR** don seisiún seo — gach ordú ceadaithe go huathoibríoch. Úsáid go cúramach."
      +
      +  shared:
      +    session_db_unavailable:      "Níl bunachar sonraí na seisiún ar fáil."
      +    session_db_unavailable_prefix: "Níl bunachar sonraí na seisiún ar fáil"
      +    session_not_found:           "Seisiún gan a aimsiú sa bhunachar sonraí."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/hu.yaml
      @@ -0,0 +1,350 @@
      +# Hermes statikus üzenetkatalógus -- Magyar
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  VESZÉLYES PARANCS: {description}"
      +  choose_long:     "      [o]egyszer  |  [s]munkamenet  |  [a]mindig  |  [d]elutasít"
      +  choose_short:    "      [o]egyszer  |  [s]munkamenet  |  [d]elutasít"
      +  prompt_long:     "      Választás [o/s/a/D]: "
      +  prompt_short:    "      Választás [o/s/D]: "
      +  timeout:         "      ⏱ Időtúllépés - parancs elutasítva"
      +  allowed_once:    "      ✓ Egyszer engedélyezve"
      +  allowed_session: "      ✓ Engedélyezve ehhez a munkamenethez"
      +  allowed_always:  "      ✓ Hozzáadva az állandó engedélylistához"
      +  denied:          "      ✗ Elutasítva"
      +  cancelled:       "      ✗ Megszakítva"
      +  blocklist_message: "Ez a parancs a feltétel nélküli tiltólistán van, és nem hagyható jóvá."
      +
      +gateway:
      +  approval_expired: "⚠️ A jóváhagyás lejárt (az ügynök már nem vár). Kérd meg az ügynököt, hogy próbálja újra."
      +  draining:         "⏳ {count} aktív ügynök befejezésére várunk az újraindítás előtt..."
      +  goal_cleared:     "✓ A cél törölve."
      +  no_active_goal:   "Nincs aktív cél."
      +  config_read_failed: "⚠️ Nem sikerült olvasni a config.yaml fájlt: {error}"
      +  config_save_failed: "⚠️ Nem sikerült menteni a konfigurációt: {error}"
      +
      +  model:
      +    error_prefix:           "Hiba: {error}"
      +    switched:               "Modell átváltva: `{model}`"
      +    provider_label:         "Szolgáltató: {provider}"
      +    context_label:          "Kontextus: {tokens} token"
      +    max_output_label:       "Max. kimenet: {tokens} token"
      +    cost_label:             "Költség: {cost}"
      +    capabilities_label:     "Képességek: {capabilities}"
      +    prompt_caching_enabled: "Prompt-gyorsítótárazás: bekapcsolva"
      +    warning_prefix:         "Figyelmeztetés: {warning}"
      +    saved_global:           "Mentve a config.yaml fájlba (`--global`)"
      +    session_only_hint:      "_(csak ehhez a munkamenethez — add hozzá a `--global` opciót a megőrzéshez)_"
      +    current_label:          "Aktuális: `{model}` ezen: {provider}"
      +    current_tag:            " (aktuális)"
      +    more_models_suffix:     " (+{count} további)"
      +    usage_switch_model:     "`/model <name>` — modell váltása"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — szolgáltató váltása"
      +    usage_persist:          "`/model <name> --global` — megőrzés"
      +
      +  agents:
      +    header:                "🤖 **Aktív ügynökök és feladatok**"
      +    active_agents:         "**Aktív ügynökök:** {count}"
      +    this_chat:             " · ez a csevegés"
      +    more:                  "... és még {count}"
      +    running_processes:     "**Futó háttérfolyamatok:** {count}"
      +    async_jobs:            "**Átjáró aszinkron feladatai:** {count}"
      +    none:                  "Nincsenek aktív ügynökök vagy futó feladatok."
      +    state_starting:        "indul"
      +    state_running:         "fut"
      +
      +  approve:
      +    no_pending:            "Nincs jóváhagyásra váró parancs."
      +    once_singular:         "✅ Parancs jóváhagyva. Az ügynök folytatja..."
      +    once_plural:           "✅ Parancsok jóváhagyva ({count} parancs). Az ügynök folytatja..."
      +    session_singular:      "✅ Parancs jóváhagyva (minta jóváhagyva ehhez a munkamenethez). Az ügynök folytatja..."
      +    session_plural:        "✅ Parancsok jóváhagyva (minta jóváhagyva ehhez a munkamenethez) ({count} parancs). Az ügynök folytatja..."
      +    always_singular:       "✅ Parancs jóváhagyva (minta véglegesen jóváhagyva). Az ügynök folytatja..."
      +    always_plural:         "✅ Parancsok jóváhagyva (minta véglegesen jóváhagyva) ({count} parancs). Az ügynök folytatja..."
      +
      +  background:
      +    usage:                 "Használat: /background <prompt>\nPélda: /background Foglald össze a mai legjobb HN sztorikat\n\nKülön munkamenetben futtatja a promptot. Folytathatod a beszélgetést — az eredmény itt jelenik meg, amint elkészül."
      +    started:               "🔄 Háttérfeladat elindítva: \"{preview}\"\nFeladatazonosító: {task_id}\nFolytathatod a beszélgetést — az eredmények itt jelennek meg, amint elkészülnek."
      +
      +  branch:
      +    db_unavailable:        "A munkamenet-adatbázis nem érhető el."
      +    no_conversation:       "Nincs elágaztatható beszélgetés — küldj előbb egy üzenetet."
      +    create_failed:         "Nem sikerült létrehozni az ágat: {error}"
      +    switch_failed:         "Az ág létrejött, de nem sikerült rá váltani."
      +    branched_one:          "⑂ Új ág: **{title}** ({count} üzenet másolva)\nEredeti: `{parent}`\nÁg: `{new}`\nHasználd a `/resume` parancsot az eredetihez való visszatéréshez."
      +    branched_many:         "⑂ Új ág: **{title}** ({count} üzenet másolva)\nEredeti: `{parent}`\nÁg: `{new}`\nHasználd a `/resume` parancsot az eredetihez való visszatéréshez."
      +
      +  commands:
      +    usage:                 "Használat: `/commands [page]`"
      +    skill_header:          "⚡ **Készségparancsok**:"
      +    default_desc:          "Készségparancs"
      +    none:                  "Nincsenek elérhető parancsok."
      +    header:                "📚 **Parancsok** (összesen {total}, {page}/{total_pages}. oldal)"
      +    nav_prev:              "`/commands {page}` ← előző"
      +    nav_next:              "következő → `/commands {page}`"
      +    out_of_range:          "_(A kért {requested}. oldal a tartományon kívül esik, a(z) {page}. oldal jelenik meg.)_"
      +
      +  compress:
      +    not_enough:            "Nincs elég beszélgetés a tömörítéshez (legalább 4 üzenet kell)."
      +    no_provider:           "Nincs konfigurált szolgáltató — nem lehet tömöríteni."
      +    nothing_to_do:         "Még nincs mit tömöríteni (a teljes átirat még védett kontextus)."
      +    focus_line:            "Fókusz: \"{topic}\""
      +    summary_failed:        "⚠️ Az összefoglaló generálása sikertelen ({error}). {count} korábbi üzenet eltávolítva és helykitöltővel helyettesítve; a korábbi kontextus már nem helyreállítható. Érdemes ellenőrizni az auxiliary.compression modell konfigurációját."
      +    aux_failed:            "ℹ️ A beállított tömörítőmodell (`{model}`) hibát adott ({error}). A főmodellel helyreállítva — a kontextus érintetlen — de érdemes ellenőrizni az `auxiliary.compression.model` beállítást a config.yaml fájlban."
      +    failed:                "Tömörítés sikertelen: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Nem sikerült feltölteni a hibakeresési jelentést: {error}"
      +    header:                "**Hibakeresési jelentés feltöltve:**"
      +    auto_delete:           "⏱ A beillesztések 6 óra múlva automatikusan törlődnek."
      +    full_logs_hint:        "Teljes naplók feltöltéséhez használd a `hermes debug share` parancsot a CLI-ből."
      +    share_hint:            "Oszd meg ezeket a hivatkozásokat a Hermes csapattal támogatásért."
      +
      +  deny:
      +    stale:                 "❌ Parancs elutasítva (a jóváhagyás elavult)."
      +    no_pending:            "Nincs elutasítható függőben lévő parancs."
      +    denied_singular:       "❌ Parancs elutasítva."
      +    denied_plural:         "❌ Parancsok elutasítva ({count} parancs)."
      +
      +  fast:
      +    not_supported:         "⚡ A /fast csak olyan OpenAI modelleknél érhető el, amelyek támogatják a Priority Processinget."
      +    status:                "⚡ Priority Processing\n\nJelenlegi mód: `{mode}`\n\n_Használat:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Ismeretlen argumentum: `{arg}`\n\n**Érvényes lehetőségek:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (mentve a konfigurációba)\n_(a következő üzenettől lép életbe)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (csak ebben a munkamenetben)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Futási idejű lábléc: **{state}**\nMezők: `{fields}`\nPlatform: `{platform}`"
      +    usage:                 "Használat: `/footer [on|off|status]`"
      +    saved:                 "📎 Futási idejű lábléc: **{state}**{example}\n_(globálisan elmentve — a következő üzenettől lép életbe)_"
      +    example_line:          "\nPélda: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "A célok nem érhetők el ebben a munkamenetben."
      +    no_goal_set:           "Nincs cél beállítva."
      +    paused:                "⏸ Cél szüneteltetve: {goal}"
      +    no_resume:             "Nincs folytatható cél."
      +    resumed:               "▶ Cél folytatva: {goal}\nKüldj bármilyen üzenetet a folytatáshoz, vagy várj — a következő körben megteszem a következő lépést."
      +    invalid:               "Érvénytelen cél: {error}"
      +    set:                   "⊙ Cél beállítva ({budget} körös keret): {goal}\nDolgozni fogok rajta, amíg a cél el nem készül, te nem szünetelteted/törlöd, vagy a keret ki nem merül.\nVezérlés: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes parancsok**\n"
      +    skill_header:          "\n⚡ **Készségparancsok** ({count} aktív):"
      +    more_use_commands:     "\n... és még {count}. Használd a `/commands` parancsot a teljes, lapozható listához."
      +
      +  insights:
      +    invalid_days:          "Érvénytelen --days érték: {value}"
      +    error:                 "Hiba a betekintések generálásakor: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban hiba: {error}"
      +    subscribed_suffix:     "(feliratkozva — értesítést kapsz, ha a {task_id} befejeződik vagy elakad)"
      +    truncated_suffix:      "… (csonkítva; használd a `hermes kanban …` parancsot a terminálban a teljes kimenethez)"
      +    no_output:             "(nincs kimenet)"
      +
      +  personality:
      +    none_configured:       "Nincs személyiség beállítva itt: `{path}/config.yaml`"
      +    header:                "🎭 **Elérhető személyiségek**\n"
      +    none_option:           "• `none` — (nincs személyiségréteg)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nHasználat: `/personality <name>`"
      +    save_failed:           "⚠️ Nem sikerült menteni a személyiség módosítását: {error}"
      +    cleared:               "🎭 Személyiség törölve — alap ügynöki viselkedés használatban.\n_(a következő üzenettől lép életbe)_"
      +    set_to:                "🎭 Személyiség beállítva: **{name}**\n_(a következő üzenettől lép életbe)_"
      +    unknown:               "Ismeretlen személyiség: `{name}`\n\nElérhetők: {available}"
      +
      +  profile:
      +    header:                "👤 **Profil:** `{profile}`"
      +    home:                  "📂 **Kezdőkönyvtár:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (alapértelmezett)"
      +    level_disabled:            "none (kikapcsolva)"
      +    scope_session:             "munkamenet-felülbírálás"
      +    scope_global:              "globális konfiguráció"
      +    status:                    "🧠 **Gondolkodási beállítások**\n\n**Erőfeszítés:** `{level}`\n**Hatókör:** {scope}\n**Megjelenítés:** {display}\n\n_Használat:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "be ✓"
      +    display_off:               "ki"
      +    display_set_on:            "🧠 ✓ Gondolkodás megjelenítése: **BE**\nA modell gondolatai minden válasz előtt megjelennek itt: **{platform}**."
      +    display_set_off:           "🧠 ✓ Gondolkodás megjelenítése: **KI** itt: **{platform}**"
      +    reset_global_unsupported:  "⚠️ A `/reasoning reset --global` nem támogatott. Használd a `/reasoning <level> --global` parancsot a globális alapérték módosításához."
      +    reset_done:                "🧠 ✓ A munkamenet gondolkodási felülbírálása törölve; visszaállás a globális konfigurációra."
      +    unknown_arg:               "⚠️ Ismeretlen argumentum: `{arg}`\n\n**Érvényes szintek:** none, minimal, low, medium, high, xhigh\n**Megjelenítés:** show, hide\n**Megőrzés:** add hozzá a `--global` opciót a munkameneten túli mentéshez"
      +    set_global:                "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (mentve a konfigurációba)\n_(a következő üzenettől lép életbe)_"
      +    set_global_save_failed:    "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (csak ebben a munkamenetben — a konfiguráció mentése sikertelen)\n_(a következő üzenettől lép életbe)_"
      +    set_session:               "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (csak ebben a munkamenetben — add hozzá a `--global` opciót a megőrzéshez)\n_(a következő üzenettől lép életbe)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp megszakítva. Az MCP-eszközök változatlanok."
      +    always_followup:       "ℹ️ A jövőbeli `/reload-mcp` hívások megerősítés nélkül futnak. Újra engedélyezhető az `approvals.mcp_reload_confirm: true` beállítással a config.yaml fájlban."
      +    confirm_prompt:        "⚠️ **A /reload-mcp megerősítése**\n\nAz MCP-szerverek újratöltése újraépíti az eszközkészletet ehhez a munkamenethez, és **érvényteleníti a szolgáltató prompt-gyorsítótárát** — a következő üzenet újraküldi a teljes bemeneti tokent. Hosszú kontextusú vagy magas gondolkodási szintű modelleknél ez költséges lehet.\n\nVálassz:\n• **Egyszeri jóváhagyás** — újratöltés most\n• **Mindig jóváhagy** — újratöltés most, és ennek a kérdésnek a végleges elnémítása\n• **Megszakítás** — az MCP-eszközök változatlanok maradnak\n\n_Szöveges alternatíva: válaszolj `/approve`, `/always` vagy `/cancel` paranccsal._"
      +    header:                "🔄 **MCP-szerverek újratöltve**\n"
      +    reconnected:           "♻️ Újracsatlakozva: {names}"
      +    added:                 "➕ Hozzáadva: {names}"
      +    removed:               "➖ Eltávolítva: {names}"
      +    none_connected:        "Nincsenek csatlakoztatott MCP-szerverek."
      +    tools_available:       "\n🔧 {tools} eszköz érhető el {servers} szerverről"
      +    failed:                "❌ MCP újratöltés sikertelen: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Készségek újratöltve**\n"
      +    no_new:                "Nem észleltünk új készséget."
      +    total:                 "\n📚 {count} készség érhető el"
      +    added_header:          "➕ **Hozzáadott készségek:**"
      +    removed_header:        "➖ **Eltávolított készségek:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Készségek újratöltése sikertelen: {error}"
      +
      +  reset:
      +    header_default:        "✨ Munkamenet visszaállítva! Kezdjük tiszta lappal."
      +    header_new:            "✨ Új munkamenet elindítva!"
      +    header_titled:         "✨ Új munkamenet elindítva: {title}"
      +    title_rejected:        "\n⚠️ Cím elutasítva: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — a munkamenet cím nélkül indult."
      +    title_empty_untitled:  "\n⚠️ Tisztítás után a cím üres — a munkamenet cím nélkül indult."
      +    tip:                   "\n✦ Tipp: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Az átjáró újraindítása már folyamatban van..."
      +    restarting:            "♻ Átjáró újraindítása. Ha 60 másodpercen belül nem kapsz értesítést, indítsd újra a konzolból a `hermes gateway restart` paranccsal."
      +
      +  resume:
      +    db_unavailable:        "A munkamenet-adatbázis nem érhető el."
      +    no_named_sessions:     "Nem található elnevezett munkamenet.\nHasználd a `/title Saját munkamenet` parancsot a jelenlegi munkamenet elnevezéséhez, majd a `/resume Saját munkamenet` paranccsal térhetsz vissza hozzá."
      +    list_header:           "📋 **Elnevezett munkamenetek**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nHasználat: `/resume <munkamenet neve>`"
      +    list_failed:           "Nem sikerült listázni a munkameneteket: {error}"
      +    not_found:             "Nem található '**{name}**' nevű munkamenet.\nArgumentumok nélkül használd a `/resume` parancsot az elérhető munkamenetek megtekintéséhez."
      +    already_on:            "📌 Már a **{name}** munkamenetben vagy."
      +    switch_failed:         "Nem sikerült munkamenetet váltani."
      +    resumed_one:           "↻ **{title}** munkamenet folytatva ({count} üzenet). Beszélgetés visszaállítva."
      +    resumed_many:          "↻ **{title}** munkamenet folytatva ({count} üzenet). Beszélgetés visszaállítva."
      +    resumed_no_count:      "↻ **{title}** munkamenet folytatva. Beszélgetés visszaállítva."
      +
      +  retry:
      +    no_previous:           "Nincs előző üzenet az újrapróbáláshoz."
      +
      +  rollback:
      +    not_enabled:           "Az ellenőrzőpontok nincsenek bekapcsolva.\nKapcsold be a config.yaml fájlban:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Nem található ellenőrzőpont ehhez: {cwd}"
      +    invalid_number:        "Érvénytelen ellenőrzőpont-szám. Használj 1-{max} közötti értéket."
      +    restored:              "✅ Visszaállítva a(z) {hash} ellenőrzőpontra: {reason}\nA visszaállítás előtti pillanatkép automatikusan elmentve."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Nem sikerült menteni a kezdőcsatornát: {error}"
      +    success:               "✅ Kezdőcsatorna beállítva: **{name}** (ID: {chat_id}).\nA cron-feladatok és a platformok közötti üzenetek ide érkeznek."
      +
      +  status:
      +    header:                "📊 **Hermes Gateway állapot**"
      +    session_id:            "**Munkamenet-azonosító:** `{session_id}`"
      +    title:                 "**Cím:** {title}"
      +    created:               "**Létrehozva:** {timestamp}"
      +    last_activity:         "**Utolsó tevékenység:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Ügynök fut:** {state}"
      +    state_yes:             "Igen ⚡"
      +    state_no:              "Nem"
      +    queued:                "**Sorban álló folytatások:** {count}"
      +    platforms:             "**Csatlakoztatott platformok:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Leállítva. Az ügynök még el sem kezdte — folytathatod ezt a munkamenetet."
      +    stopped:               "⚡ Leállítva. Folytathatod ezt a munkamenetet."
      +    no_active:             "Nincs leállítható aktív feladat."
      +
      +  title:
      +    db_unavailable:        "A munkamenet-adatbázis nem érhető el."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Tisztítás után a cím üres. Használj nyomtatható karaktereket."
      +    set_to:                "✏️ Munkamenet címe beállítva: **{title}**"
      +    not_found:             "A munkamenet nem található az adatbázisban."
      +    current_with_title:    "📌 Munkamenet: `{session_id}`\nCím: **{title}**"
      +    current_no_title:      "📌 Munkamenet: `{session_id}`\nNincs cím beállítva. Használat: `/title Saját munkamenet neve`"
      +
      +  topic:
      +    not_telegram_dm:         "A /topic parancs csak Telegram privát csevegésekben érhető el."
      +    no_session_db:           "A munkamenet-adatbázis nem érhető el."
      +    unauthorized:            "Nincs jogosultságod a /topic használatához ezen a boton."
      +    restore_needs_topic:     "Egy munkamenet visszaállításához először hozz létre vagy nyiss meg egy Telegram topicot, majd küldd a /topic <session-id> parancsot abban a topicban. Új topic létrehozásához nyisd meg az All Messagest, és küldj oda bármilyen üzenetet."
      +    topics_disabled:         "A Telegram topicok még nincsenek engedélyezve ehhez a bothoz.\n\nHogyan engedélyezd:\n1. Nyisd meg a @BotFathert.\n2. Válaszd ki a botod.\n3. Nyisd meg a Bot Settings → Threads Settings menüt.\n4. Kapcsold be a Threaded Mode-ot, és győződj meg róla, hogy a felhasználók új threadeket hozhatnak létre.\n\nEzután küldd újra a /topic parancsot."
      +    topics_user_disallowed:  "A Telegram topicok engedélyezve vannak, de a felhasználók nem hozhatnak létre topicokat.\n\nNyisd meg a @BotFather → válaszd ki a botod → Bot Settings → Threads Settings menüt, majd kapcsold ki a 'Disallow users to create new threads' opciót.\n\nEzután küldd újra a /topic parancsot."
      +    enable_failed:           "Nem sikerült engedélyezni a Telegram topic módot: {error}"
      +    bound_status:            "Ez a topic ehhez van kapcsolva:\nMunkamenet: {label}\nID: {session_id}\n\nHasználd a /new parancsot, hogy lecseréld ezt a topicot új munkamenetre.\nPárhuzamos munkához nyisd meg az All Messagest, és küldj oda egy üzenetet egy másik topic létrehozásához."
      +    thread_ready:            "A többmunkamenetes Telegram topicok engedélyezve vannak.\n\nEz a topic független Hermes-munkamenetként szolgál. Használd a /new parancsot, hogy lecseréld a topic jelenlegi munkamenetét. Párhuzamos munkához nyisd meg az All Messagest, és küldj oda egy üzenetet egy másik topic létrehozásához."
      +    untitled_session:        "Cím nélküli munkamenet"
      +
      +  undo:
      +    nothing:               "Nincs mit visszavonni."
      +    removed:               "↩️ {count} üzenet visszavonva.\nEltávolítva: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ A /update csak üzenetküldő platformokról érhető el. Futtasd a `hermes update` parancsot a terminálból."
      +    not_git_repo:            "✗ Nem git-tárhely — frissítés nem lehetséges."
      +    hermes_cmd_not_found:    "✗ Nem sikerült megtalálni a `hermes` parancsot. A Hermes fut, de a frissítőparancs nem találta a futtatható fájlt a PATH-on vagy a jelenlegi Python interpreteren keresztül. Próbáld futtatni a `hermes update` parancsot manuálisan a terminálban."
      +    start_failed:            "✗ Nem sikerült elindítani a frissítést: {error}"
      +    starting:                "⚕ Hermes frissítés indítása… A folyamatot itt fogom közvetíteni."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Sebességkorlátok:** {state}"
      +    header_session:           "📊 **Munkamenet tokenhasználat**"
      +    label_model:              "Modell: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] tokenek: [redacted:secret-assignment]"
      +    label_cache_read:         "Gyorsítótár-olvasási tokenek: [redacted:secret-assignment]"
      +    label_cache_write:        "Gyorsítótár-írási tokenek: [redacted:secret-assignment]"
      +    label_output_tokens:      "[redacted:secret-assignment] tokenek: [redacted:secret-assignment]"
      +    label_total:              "Összesen: {count}"
      +    label_api_calls:          "API-hívások: {count}"
      +    label_cost:               "Költség: {prefix}${amount}"
      +    label_cost_included:      "Költség: belefoglalva"
      +    label_context:            "Kontextus: {used} / {total} ({pct}%)"
      +    label_compressions:       "Tömörítések: {count}"
      +    header_session_info:      "📊 **Munkamenet-információ**"
      +    label_messages:           "Üzenetek: {count}"
      +    label_estimated_context:  "Becsült kontextus: ~{count} token"
      +    detailed_after_first:     "_(A részletes használat az első ügynökválasz után érhető el)_"
      +    no_data:                  "Ehhez a munkamenethez nincsenek elérhető használati adatok."
      +
      +  verbose:
      +    not_enabled:           "A `/verbose` parancs nincs engedélyezve az üzenetküldő platformokon.\n\nEngedélyezd a `config.yaml` fájlban:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Eszközfolyamat: **OFF** — nem jelenik meg eszközaktivitás."
      +    mode_new:              "⚙️ Eszközfolyamat: **NEW** — eszközváltáskor jelenik meg (előnézet hossza: `display.tool_preview_length`, alapértelmezetten 40)."
      +    mode_all:              "⚙️ Eszközfolyamat: **ALL** — minden eszközhívás megjelenik (előnézet hossza: `display.tool_preview_length`, alapértelmezetten 40)."
      +    mode_verbose:          "⚙️ Eszközfolyamat: **VERBOSE** — minden eszközhívás teljes argumentumokkal."
      +    saved_suffix:          "_(elmentve ehhez: **{platform}** — a következő üzenettől lép életbe)_"
      +    save_failed:           "_(nem sikerült menteni a konfigurációba: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Hangmód bekapcsolva.\nHanggal válaszolok, ha hangüzenetet küldesz.\nHasználd a /voice tts parancsot, hogy minden üzenetre hangválaszt kapj."
      +    disabled_text:         "Hangmód kikapcsolva. Csak szöveges válaszok."
      +    tts_enabled:           "Auto-TTS bekapcsolva.\nMinden válasz tartalmaz egy hangüzenetet."
      +    status_mode:           "Hangmód: {label}"
      +    status_channel:        "Hangcsatorna: #{channel}"
      +    status_participants:   "Résztvevők: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (beszél)"
      +    enabled_short:         "Hangmód bekapcsolva."
      +    disabled_short:        "Hangmód kikapcsolva."
      +    label_off:             "Ki (csak szöveg)"
      +    label_voice_only:      "Be (hangválasz hangüzenetekre)"
      +    label_all:             "TTS (hangválasz minden üzenetre)"
      +
      +  yolo:
      +    disabled:                   "⚠️ YOLO mód **KI** ebben a munkamenetben — a veszélyes parancsok jóváhagyást igényelnek."
      +    enabled:                    "⚡ YOLO mód **BE** ebben a munkamenetben — minden parancs automatikusan jóváhagyva. Óvatosan használd."
      +
      +  shared:
      +    session_db_unavailable:      "A munkamenet-adatbázis nem érhető el."
      +    session_db_unavailable_prefix: "A munkamenet-adatbázis nem érhető el"
      +    session_not_found:           "A munkamenet nem található az adatbázisban."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/it.yaml
      @@ -0,0 +1,350 @@
      +# Catalogo dei messaggi statici di Hermes -- Italiano
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  COMANDO PERICOLOSO: {description}"
      +  choose_long:     "      [o]una volta  |  [s]essione  |  [a]sempre  |  [d]nega"
      +  choose_short:    "      [o]una volta  |  [s]essione  |  [d]nega"
      +  prompt_long:     "      Scelta [o/s/a/D]: "
      +  prompt_short:    "      Scelta [o/s/D]: "
      +  timeout:         "      ⏱ Tempo scaduto — comando negato"
      +  allowed_once:    "      ✓ Consentito una volta"
      +  allowed_session: "      ✓ Consentito per questa sessione"
      +  allowed_always:  "      ✓ Aggiunto alla lista permessi permanente"
      +  denied:          "      ✗ Negato"
      +  cancelled:       "      ✗ Annullato"
      +  blocklist_message: "Questo comando è nella lista di blocco incondizionata e non può essere approvato."
      +
      +gateway:
      +  approval_expired: "⚠️ Approvazione scaduta (l'agente non è più in attesa). Chiedi all'agente di riprovare."
      +  draining:         "⏳ Attendo il completamento di {count} agente/i attivo/i prima di riavviare..."
      +  goal_cleared:     "✓ Obiettivo cancellato."
      +  no_active_goal:   "Nessun obiettivo attivo."
      +  config_read_failed: "⚠️ Impossibile leggere config.yaml: {error}"
      +  config_save_failed: "⚠️ Impossibile salvare la configurazione: {error}"
      +
      +  model:
      +    error_prefix:           "Errore: {error}"
      +    switched:               "Modello cambiato a `{model}`"
      +    provider_label:         "Provider: {provider}"
      +    context_label:          "Contesto: {tokens} token"
      +    max_output_label:       "Output massimo: {tokens} token"
      +    cost_label:             "Costo: {cost}"
      +    capabilities_label:     "Capacità: {capabilities}"
      +    prompt_caching_enabled: "Caching dei prompt: attivo"
      +    warning_prefix:         "Avviso: {warning}"
      +    saved_global:           "Salvato in config.yaml (`--global`)"
      +    session_only_hint:      "_(solo per questa sessione — aggiungi `--global` per renderlo permanente)_"
      +    current_label:          "Attuale: `{model}` su {provider}"
      +    current_tag:            " (attuale)"
      +    more_models_suffix:     " (+{count} altri)"
      +    usage_switch_model:     "`/model <name>` — cambia modello"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — cambia provider"
      +    usage_persist:          "`/model <name> --global` — rendi permanente"
      +
      +  agents:
      +    header:                "🤖 **Agenti e attività attivi**"
      +    active_agents:         "**Agenti attivi:** {count}"
      +    this_chat:             " · questa chat"
      +    more:                  "... e {count} altri"
      +    running_processes:     "**Processi in background in esecuzione:** {count}"
      +    async_jobs:            "**Job asincroni del gateway:** {count}"
      +    none:                  "Nessun agente attivo o attività in esecuzione."
      +    state_starting:        "in avvio"
      +    state_running:         "in esecuzione"
      +
      +  approve:
      +    no_pending:            "Nessun comando in attesa di approvazione."
      +    once_singular:         "✅ Comando approvato. L'agente sta riprendendo..."
      +    once_plural:           "✅ Comandi approvati ({count} comandi). L'agente sta riprendendo..."
      +    session_singular:      "✅ Comando approvato (modello approvato per questa sessione). L'agente sta riprendendo..."
      +    session_plural:        "✅ Comandi approvati (modello approvato per questa sessione) ({count} comandi). L'agente sta riprendendo..."
      +    always_singular:       "✅ Comando approvato (modello approvato in modo permanente). L'agente sta riprendendo..."
      +    always_plural:         "✅ Comandi approvati (modello approvato in modo permanente) ({count} comandi). L'agente sta riprendendo..."
      +
      +  background:
      +    usage:                 "Uso: /background <prompt>\nEsempio: /background Riassumi le principali notizie di HN di oggi\n\nEsegue il prompt in una sessione separata. Puoi continuare a chattare — il risultato apparirà qui al termine."
      +    started:               "🔄 Attività in background avviata: \"{preview}\"\nID attività: {task_id}\nPuoi continuare a chattare — i risultati appariranno al termine."
      +
      +  branch:
      +    db_unavailable:        "Database delle sessioni non disponibile."
      +    no_conversation:       "Nessuna conversazione da diramare — invia prima un messaggio."
      +    create_failed:         "Creazione del ramo non riuscita: {error}"
      +    switch_failed:         "Ramo creato ma il passaggio ad esso non è riuscito."
      +    branched_one:          "⑂ Diramato in **{title}** ({count} messaggio copiato)\nOriginale: `{parent}`\nRamo: `{new}`\nUsa `/resume` per tornare all'originale."
      +    branched_many:         "⑂ Diramato in **{title}** ({count} messaggi copiati)\nOriginale: `{parent}`\nRamo: `{new}`\nUsa `/resume` per tornare all'originale."
      +
      +  commands:
      +    usage:                 "Uso: `/commands [page]`"
      +    skill_header:          "⚡ **Comandi skill**:"
      +    default_desc:          "Comando skill"
      +    none:                  "Nessun comando disponibile."
      +    header:                "📚 **Comandi** ({total} totali, pagina {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← prec"
      +    nav_next:              "succ → `/commands {page}`"
      +    out_of_range:          "_(La pagina richiesta {requested} è fuori intervallo, mostrando la pagina {page}.)_"
      +
      +  compress:
      +    not_enough:            "Conversazione insufficiente da comprimere (servono almeno 4 messaggi)."
      +    no_provider:           "Nessun provider configurato — impossibile comprimere."
      +    nothing_to_do:         "Niente da comprimere per ora (la trascrizione è ancora tutta contesto protetto)."
      +    focus_line:            "Focus: \"{topic}\""
      +    summary_failed:        "⚠️ Generazione del riepilogo non riuscita ({error}). {count} messaggio/i storico/i sono stati rimossi e sostituiti con un segnaposto; il contesto precedente non è più recuperabile. Considera di controllare la configurazione del modello auxiliary.compression."
      +    aux_failed:            "ℹ️ Il modello di compressione configurato `{model}` non è riuscito ({error}). Recupero effettuato usando il modello principale — il contesto è intatto — ma potresti voler controllare `auxiliary.compression.model` in config.yaml."
      +    failed:                "Compressione non riuscita: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Caricamento del report di debug non riuscito: {error}"
      +    header:                "**Report di debug caricato:**"
      +    auto_delete:           "⏱ I paste verranno eliminati automaticamente tra 6 ore."
      +    full_logs_hint:        "Per il caricamento dei log completi, usa `hermes debug share` dalla CLI."
      +    share_hint:            "Condividi questi link con il team Hermes per ricevere supporto."
      +
      +  deny:
      +    stale:                 "❌ Comando negato (l'approvazione era obsoleta)."
      +    no_pending:            "Nessun comando in attesa da negare."
      +    denied_singular:       "❌ Comando negato."
      +    denied_plural:         "❌ Comandi negati ({count} comandi)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast è disponibile solo per i modelli OpenAI che supportano Priority Processing."
      +    status:                "⚡ Priority Processing\n\nModalità attuale: `{mode}`\n\n_Uso:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Argomento sconosciuto: `{arg}`\n\n**Opzioni valide:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (salvato nella configurazione)\n_(verrà applicato al prossimo messaggio)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (solo per questa sessione)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Footer di runtime: **{state}**\nCampi: `{fields}`\nPiattaforma: `{platform}`"
      +    usage:                 "Uso: `/footer [on|off|status]`"
      +    saved:                 "📎 Footer di runtime: **{state}**{example}\n_(salvato globalmente — verrà applicato al prossimo messaggio)_"
      +    example_line:          "\nEsempio: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Gli obiettivi non sono disponibili in questa sessione."
      +    no_goal_set:           "Nessun obiettivo impostato."
      +    paused:                "⏸ Obiettivo in pausa: {goal}"
      +    no_resume:             "Nessun obiettivo da riprendere."
      +    resumed:               "▶ Obiettivo ripreso: {goal}\nInvia un messaggio per continuare, oppure aspetta — farò il prossimo passo al turno successivo."
      +    invalid:               "Obiettivo non valido: {error}"
      +    set:                   "⊙ Obiettivo impostato (budget di {budget} turni): {goal}\nContinuerò a lavorare finché l'obiettivo non sarà completato, lo metterai in pausa/lo cancellerai, oppure il budget sarà esaurito.\nControlli: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Comandi Hermes**\n"
      +    skill_header:          "\n⚡ **Comandi skill** ({count} attivi):"
      +    more_use_commands:     "\n... e altri {count}. Usa `/commands` per la lista paginata completa."
      +
      +  insights:
      +    invalid_days:          "Valore --days non valido: {value}"
      +    error:                 "Errore nella generazione degli insight: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ errore kanban: {error}"
      +    subscribed_suffix:     "(iscritto — riceverai notifica quando {task_id} verrà completato o si bloccherà)"
      +    truncated_suffix:      "… (troncato; usa `hermes kanban …` nel terminale per l'output completo)"
      +    no_output:             "(nessun output)"
      +
      +  personality:
      +    none_configured:       "Nessuna personalità configurata in `{path}/config.yaml`"
      +    header:                "🎭 **Personalità disponibili**\n"
      +    none_option:           "• `none` — (nessun overlay di personalità)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nUso: `/personality <name>`"
      +    save_failed:           "⚠️ Salvataggio del cambio di personalità non riuscito: {error}"
      +    cleared:               "🎭 Personalità cancellata — uso il comportamento base dell'agente.\n_(verrà applicato al prossimo messaggio)_"
      +    set_to:                "🎭 Personalità impostata su **{name}**\n_(verrà applicato al prossimo messaggio)_"
      +    unknown:               "Personalità sconosciuta: `{name}`\n\nDisponibili: {available}"
      +
      +  profile:
      +    header:                "👤 **Profilo:** `{profile}`"
      +    home:                  "📂 **Home:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medio (predefinito)"
      +    level_disabled:            "nessuno (disattivato)"
      +    scope_session:             "override di sessione"
      +    scope_global:              "configurazione globale"
      +    status:                    "🧠 **Impostazioni di reasoning**\n\n**Sforzo:** `{level}`\n**Ambito:** {scope}\n**Visualizzazione:** {display}\n\n_Uso:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "attivo ✓"
      +    display_off:               "disattivato"
      +    display_set_on:            "🧠 ✓ Visualizzazione del reasoning: **ATTIVA**\nIl pensiero del modello verrà mostrato prima di ogni risposta su **{platform}**."
      +    display_set_off:           "🧠 ✓ Visualizzazione del reasoning: **DISATTIVATA** per **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` non è supportato. Usa `/reasoning <level> --global` per cambiare il valore predefinito globale."
      +    reset_done:                "🧠 ✓ Override di reasoning della sessione cancellato; ripristino della configurazione globale."
      +    unknown_arg:               "⚠️ Argomento sconosciuto: `{arg}`\n\n**Livelli validi:** none, minimal, low, medium, high, xhigh\n**Visualizzazione:** show, hide\n**Persistenza:** aggiungi `--global` per salvare oltre questa sessione"
      +    set_global:                "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (salvato nella configurazione)\n_(verrà applicato al prossimo messaggio)_"
      +    set_global_save_failed:    "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (solo per questa sessione — salvataggio della configurazione non riuscito)\n_(verrà applicato al prossimo messaggio)_"
      +    set_session:               "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (solo per questa sessione — aggiungi `--global` per renderlo permanente)\n_(verrà applicato al prossimo messaggio)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp annullato. Strumenti MCP invariati."
      +    always_followup:       "ℹ️ Le future chiamate a `/reload-mcp` verranno eseguite senza conferma. Riattiva tramite `approvals.mcp_reload_confirm: true` in config.yaml."
      +    confirm_prompt:        "⚠️ **Conferma /reload-mcp**\n\nIl ricaricamento dei server MCP ricostruisce il set di strumenti per questa sessione e **invalida la cache dei prompt del provider** — il prossimo messaggio invierà nuovamente tutti i token di input. Sui modelli a contesto lungo o ad alto reasoning questo può essere costoso.\n\nScegli:\n• **Approva una volta** — ricarica ora\n• **Approva sempre** — ricarica ora e silenzia questa richiesta in modo permanente\n• **Annulla** — lascia gli strumenti MCP invariati\n\n_Alternativa testuale: rispondi `/approve`, `/always`, oppure `/cancel`._"
      +    header:                "🔄 **Server MCP ricaricati**\n"
      +    reconnected:           "♻️ Riconnessi: {names}"
      +    added:                 "➕ Aggiunti: {names}"
      +    removed:               "➖ Rimossi: {names}"
      +    none_connected:        "Nessun server MCP connesso."
      +    tools_available:       "\n🔧 {tools} strumento/i disponibile/i da {servers} server"
      +    failed:                "❌ Ricaricamento MCP non riuscito: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skill ricaricate**\n"
      +    no_new:                "Nessuna nuova skill rilevata."
      +    total:                 "\n📚 {count} skill disponibili"
      +    added_header:          "➕ **Skill aggiunte:**"
      +    removed_header:        "➖ **Skill rimosse:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Ricaricamento delle skill non riuscito: {error}"
      +
      +  reset:
      +    header_default:        "✨ Sessione reimpostata! Si ricomincia da zero."
      +    header_new:            "✨ Nuova sessione avviata!"
      +    header_titled:         "✨ Nuova sessione avviata: {title}"
      +    title_rejected:        "\n⚠️ Titolo rifiutato: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — sessione avviata senza titolo."
      +    title_empty_untitled:  "\n⚠️ Il titolo è vuoto dopo la pulizia — sessione avviata senza titolo."
      +    tip:                   "\n✦ Suggerimento: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Riavvio del gateway già in corso..."
      +    restarting:            "♻ Riavvio del gateway. Se non ricevi una notifica entro 60 secondi, riavvia dalla console con `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Database delle sessioni non disponibile."
      +    no_named_sessions:     "Nessuna sessione con nome trovata.\nUsa `/title My Session` per dare un nome alla sessione attuale, poi `/resume My Session` per tornare a essa in seguito."
      +    list_header:           "📋 **Sessioni con nome**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nUso: `/resume <session name>`"
      +    list_failed:           "Impossibile elencare le sessioni: {error}"
      +    not_found:             "Nessuna sessione trovata corrispondente a '**{name}**'.\nUsa `/resume` senza argomenti per vedere le sessioni disponibili."
      +    already_on:            "📌 Già nella sessione **{name}**."
      +    switch_failed:         "Cambio di sessione non riuscito."
      +    resumed_one:           "↻ Sessione **{title}** ripresa ({count} messaggio). Conversazione ripristinata."
      +    resumed_many:          "↻ Sessione **{title}** ripresa ({count} messaggi). Conversazione ripristinata."
      +    resumed_no_count:      "↻ Sessione **{title}** ripresa. Conversazione ripristinata."
      +
      +  retry:
      +    no_previous:           "Nessun messaggio precedente da ripetere."
      +
      +  rollback:
      +    not_enabled:           "I checkpoint non sono abilitati.\nAbilitali in config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Nessun checkpoint trovato per {cwd}"
      +    invalid_number:        "Numero di checkpoint non valido. Usa 1-{max}."
      +    restored:              "✅ Ripristinato al checkpoint {hash}: {reason}\nUno snapshot pre-rollback è stato salvato automaticamente."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Salvataggio del canale home non riuscito: {error}"
      +    success:               "✅ Canale home impostato su **{name}** (ID: {chat_id}).\nI cron job e i messaggi cross-platform verranno consegnati qui."
      +
      +  status:
      +    header:                "📊 **Stato del Gateway Hermes**"
      +    session_id:            "**ID sessione:** `{session_id}`"
      +    title:                 "**Titolo:** {title}"
      +    created:               "**Creata:** {timestamp}"
      +    last_activity:         "**Ultima attività:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agente in esecuzione:** {state}"
      +    state_yes:             "Sì ⚡"
      +    state_no:              "No"
      +    queued:                "**Follow-up in coda:** {count}"
      +    platforms:             "**Piattaforme connesse:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Fermato. L'agente non era ancora partito — puoi continuare questa sessione."
      +    stopped:               "⚡ Fermato. Puoi continuare questa sessione."
      +    no_active:             "Nessuna attività attiva da fermare."
      +
      +  title:
      +    db_unavailable:        "Database delle sessioni non disponibile."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Il titolo è vuoto dopo la pulizia. Usa caratteri stampabili."
      +    set_to:                "✏️ Titolo della sessione impostato: **{title}**"
      +    not_found:             "Sessione non trovata nel database."
      +    current_with_title:    "📌 Sessione: `{session_id}`\nTitolo: **{title}**"
      +    current_no_title:      "📌 Sessione: `{session_id}`\nNessun titolo impostato. Uso: `/title My Session Name`"
      +
      +  topic:
      +    not_telegram_dm:         "Il comando /topic è disponibile solo nelle chat private di Telegram."
      +    no_session_db:           "Database delle sessioni non disponibile."
      +    unauthorized:            "Non sei autorizzato a usare /topic su questo bot."
      +    restore_needs_topic:     "Per ripristinare una sessione, crea o apri prima un topic Telegram, poi invia /topic <session-id> all'interno di quel topic. Per creare un nuovo topic, apri All Messages e invia un messaggio qualsiasi lì."
      +    topics_disabled:         "I topic Telegram non sono ancora abilitati per questo bot.\n\nCome abilitarli:\n1. Apri @BotFather.\n2. Scegli il tuo bot.\n3. Apri Bot Settings → Threads Settings.\n4. Attiva la modalità Threaded e assicurati che gli utenti possano creare nuovi thread.\n\nPoi invia di nuovo /topic."
      +    topics_user_disallowed:  "I topic Telegram sono abilitati, ma agli utenti non è permesso crearne.\n\nApri @BotFather → scegli il tuo bot → Bot Settings → Threads Settings, poi disattiva 'Disallow users to create new threads'.\n\nPoi invia di nuovo /topic."
      +    enable_failed:           "Abilitazione della modalità topic Telegram non riuscita: {error}"
      +    bound_status:            "Questo topic è collegato a:\nSessione: {label}\nID: {session_id}\n\nUsa /new per sostituire questo topic con una nuova sessione.\nPer lavorare in parallelo, apri All Messages e invia un messaggio lì per creare un altro topic."
      +    thread_ready:            "I topic multi-sessione di Telegram sono abilitati.\n\nQuesto topic verrà usato come una sessione Hermes indipendente. Usa /new per sostituire la sessione corrente di questo topic. Per lavorare in parallelo, apri All Messages e invia un messaggio lì per creare un altro topic."
      +    untitled_session:        "Sessione senza titolo"
      +
      +  undo:
      +    nothing:               "Niente da annullare."
      +    removed:               "↩️ Annullati {count} messaggio/i.\nRimosso: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update è disponibile solo dalle piattaforme di messaggistica. Esegui `hermes update` dal terminale."
      +    not_git_repo:            "✗ Non è un repository git — impossibile aggiornare."
      +    hermes_cmd_not_found:    "✗ Impossibile localizzare il comando `hermes`. Hermes è in esecuzione, ma il comando di aggiornamento non ha trovato l'eseguibile nel PATH o tramite l'interprete Python attuale. Prova a eseguire `hermes update` manualmente nel terminale."
      +    start_failed:            "✗ Avvio dell'aggiornamento non riuscito: {error}"
      +    starting:                "⚕ Avvio dell'aggiornamento di Hermes… mostrerò qui i progressi in streaming."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Limiti di frequenza:** {state}"
      +    header_session:           "📊 **Uso dei token della sessione**"
      +    label_model:              "Modello: `{model}`"
      +    label_input_tokens:       "Token di input: {count}"
      +    label_cache_read:         "Token di lettura cache: {count}"
      +    label_cache_write:        "Token di scrittura cache: {count}"
      +    label_output_tokens:      "Token di output: {count}"
      +    label_total:              "Totale: {count}"
      +    label_api_calls:          "Chiamate API: {count}"
      +    label_cost:               "Costo: {prefix}${amount}"
      +    label_cost_included:      "Costo: incluso"
      +    label_context:            "Contesto: {used} / {total} ({pct}%)"
      +    label_compressions:       "Compressioni: {count}"
      +    header_session_info:      "📊 **Info sessione**"
      +    label_messages:           "Messaggi: {count}"
      +    label_estimated_context:  "Contesto stimato: ~{count} token"
      +    detailed_after_first:     "_(L'uso dettagliato sarà disponibile dopo la prima risposta dell'agente)_"
      +    no_data:                  "Nessun dato di utilizzo disponibile per questa sessione."
      +
      +  verbose:
      +    not_enabled:           "Il comando `/verbose` non è abilitato per le piattaforme di messaggistica.\n\nAbilitalo in `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Progresso strumenti: **OFF** — nessuna attività degli strumenti mostrata."
      +    mode_new:              "⚙️ Progresso strumenti: **NEW** — mostrato quando lo strumento cambia (lunghezza anteprima: `display.tool_preview_length`, predefinito 40)."
      +    mode_all:              "⚙️ Progresso strumenti: **ALL** — ogni chiamata a uno strumento viene mostrata (lunghezza anteprima: `display.tool_preview_length`, predefinito 40)."
      +    mode_verbose:          "⚙️ Progresso strumenti: **VERBOSE** — ogni chiamata a uno strumento con argomenti completi."
      +    saved_suffix:          "_(salvato per **{platform}** — verrà applicato al prossimo messaggio)_"
      +    save_failed:           "_(impossibile salvare nella configurazione: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Modalità vocale attivata.\nRisponderò con la voce quando invii messaggi vocali.\nUsa /voice tts per ricevere risposte vocali per tutti i messaggi."
      +    disabled_text:         "Modalità vocale disattivata. Risposte solo testuali."
      +    tts_enabled:           "Auto-TTS attivato.\nTutte le risposte includeranno un messaggio vocale."
      +    status_mode:           "Modalità vocale: {label}"
      +    status_channel:        "Canale vocale: #{channel}"
      +    status_participants:   "Partecipanti: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (sta parlando)"
      +    enabled_short:         "Modalità vocale attivata."
      +    disabled_short:        "Modalità vocale disattivata."
      +    label_off:             "Off (solo testo)"
      +    label_voice_only:      "On (risposta vocale ai messaggi vocali)"
      +    label_all:             "TTS (risposta vocale a tutti i messaggi)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Modalità YOLO **OFF** per questa sessione — i comandi pericolosi richiederanno approvazione."
      +    enabled:                    "⚡ Modalità YOLO **ON** per questa sessione — tutti i comandi auto-approvati. Usa con cautela."
      +
      +  shared:
      +    session_db_unavailable:      "Database delle sessioni non disponibile."
      +    session_db_unavailable_prefix: "Database delle sessioni non disponibile"
      +    session_not_found:           "Sessione non trovata nel database."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/ja.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "アクティブな目標はありません。"
         config_read_failed: "⚠️ config.yaml を読み込めませんでした: {error}"
         config_save_failed: "⚠️ 設定を保存できませんでした: {error}"
      +
      +  model:
      +    error_prefix:           "エラー: {error}"
      +    switched:               "モデルを `{model}` に切り替えました"
      +    provider_label:         "プロバイダー: {provider}"
      +    context_label:          "コンテキスト: {tokens} トークン"
      +    max_output_label:       "最大出力: {tokens} トークン"
      +    cost_label:             "コスト: {cost}"
      +    capabilities_label:     "機能: {capabilities}"
      +    prompt_caching_enabled: "プロンプトキャッシュ: 有効"
      +    warning_prefix:         "警告: {warning}"
      +    saved_global:           "config.yaml に保存しました (`--global`)"
      +    session_only_hint:      "_(このセッションのみ — 永続化するには `--global` を追加)_"
      +    current_label:          "現在: `{model}` ({provider})"
      +    current_tag:            " (現在)"
      +    more_models_suffix:     " (他 {count} 件)"
      +    usage_switch_model:     "`/model <name>` — モデルを切り替え"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — プロバイダーを切り替え"
      +    usage_persist:          "`/model <name> --global` — 永続化"
      +
      +  agents:
      +    header:                "🤖 **アクティブなエージェントとタスク**"
      +    active_agents:         "**アクティブなエージェント:** {count}"
      +    this_chat:             " · このチャット"
      +    more:                  "... 他に {count} 件"
      +    running_processes:     "**実行中のバックグラウンドプロセス:** {count}"
      +    async_jobs:            "**ゲートウェイ非同期ジョブ:** {count}"
      +    none:                  "アクティブなエージェントや実行中のタスクはありません。"
      +    state_starting:        "起動中"
      +    state_running:         "実行中"
      +
      +  approve:
      +    no_pending:            "承認待ちのコマンドはありません。"
      +    once_singular:         "✅ コマンドを承認しました。エージェントを再開しています..."
      +    once_plural:           "✅ コマンドを承認しました ({count} 件)。エージェントを再開しています..."
      +    session_singular:      "✅ コマンドを承認しました (このセッション中はパターンを許可)。エージェントを再開しています..."
      +    session_plural:        "✅ コマンドを承認しました (このセッション中はパターンを許可) ({count} 件)。エージェントを再開しています..."
      +    always_singular:       "✅ コマンドを承認しました (パターンを永続的に許可)。エージェントを再開しています..."
      +    always_plural:         "✅ コマンドを承認しました (パターンを永続的に許可) ({count} 件)。エージェントを再開しています..."
      +
      +  background:
      +    usage:                 "使い方: /background <プロンプト>\n例: /background 今日の HN トップ記事を要約して\n\nプロンプトを別のセッションで実行します。チャットを続けられます — 完了したらここに結果が表示されます。"
      +    started:               "🔄 バックグラウンドタスクを開始しました: 「{preview}」\nタスク ID: {task_id}\nチャットを続けられます — 完了したらここに結果が表示されます。"
      +
      +  branch:
      +    db_unavailable:        "セッションデータベースは利用できません。"
      +    no_conversation:       "分岐する会話がありません — まずメッセージを送信してください。"
      +    create_failed:         "ブランチの作成に失敗しました: {error}"
      +    switch_failed:         "ブランチは作成されましたが、切り替えに失敗しました。"
      +    branched_one:          "⑂ **{title}** に分岐しました ({count} メッセージをコピー)\n元: `{parent}`\nブランチ: `{new}`\n元のセッションに戻るには `/resume` を使用してください。"
      +    branched_many:         "⑂ **{title}** に分岐しました ({count} メッセージをコピー)\n元: `{parent}`\nブランチ: `{new}`\n元のセッションに戻るには `/resume` を使用してください。"
      +
      +  commands:
      +    usage:                 "使い方: `/commands [page]`"
      +    skill_header:          "⚡ **スキルコマンド**:"
      +    default_desc:          "スキルコマンド"
      +    none:                  "利用可能なコマンドはありません。"
      +    header:                "📚 **コマンド** (合計 {total}、{page}/{total_pages} ページ)"
      +    nav_prev:              "`/commands {page}` ← 前へ"
      +    nav_next:              "次へ → `/commands {page}`"
      +    out_of_range:          "_(要求されたページ {requested} は範囲外のため、{page} ページを表示しています。)_"
      +
      +  compress:
      +    not_enough:            "圧縮するための会話が不十分です (少なくとも 4 件のメッセージが必要)。"
      +    no_provider:           "プロバイダーが構成されていません — 圧縮できません。"
      +    nothing_to_do:         "まだ圧縮するものがありません (トランスクリプトはすべて保護されたコンテキストのままです)。"
      +    focus_line:            "フォーカス: \"{topic}\""
      +    summary_failed:        "⚠️ 要約の生成に失敗しました ({error})。{count} 件の履歴メッセージが削除され、プレースホルダーに置き換えられました。以前のコンテキストは復元できません。auxiliary.compression モデルの設定を確認してください。"
      +    aux_failed:            "ℹ️ 構成された圧縮モデル `{model}` が失敗しました ({error})。メインモデルで復旧しました — コンテキストは無傷です — config.yaml の `auxiliary.compression.model` を確認するとよいでしょう。"
      +    failed:                "圧縮に失敗しました: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ デバッグレポートのアップロードに失敗しました: {error}"
      +    header:                "**デバッグレポートをアップロードしました:**"
      +    auto_delete:           "⏱ ペーストは 6 時間後に自動削除されます。"
      +    full_logs_hint:        "完全なログのアップロードには、CLI から `hermes debug share` を使用してください。"
      +    share_hint:            "サポートを受けるには、このリンクを Hermes チームに共有してください。"
      +
      +  deny:
      +    stale:                 "❌ コマンドを拒否しました (承認は期限切れでした)。"
      +    no_pending:            "拒否待ちのコマンドはありません。"
      +    denied_singular:       "❌ コマンドを拒否しました。"
      +    denied_plural:         "❌ コマンドを拒否しました ({count} 件)。"
      +
      +  fast:
      +    not_supported:         "⚡ /fast は Priority Processing をサポートする OpenAI モデルでのみ利用できます。"
      +    status:                "⚡ Priority Processing\n\n現在のモード: `{mode}`\n\n_使い方:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ 不明な引数: `{arg}`\n\n**有効なオプション:** normal、fast、status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (設定に保存しました)\n_(次のメッセージから有効)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (このセッションのみ)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 ランタイムフッター: **{state}**\nフィールド: `{fields}`\nプラットフォーム: `{platform}`"
      +    usage:                 "使い方: `/footer [on|off|status]`"
      +    saved:                 "📎 ランタイムフッター: **{state}**{example}\n_(グローバルに保存しました — 次のメッセージから有効)_"
      +    example_line:          "\n例: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "このセッションでは目標機能を利用できません。"
      +    no_goal_set:           "目標が設定されていません。"
      +    paused:                "⏸ 目標を一時停止しました: {goal}"
      +    no_resume:             "再開する目標がありません。"
      +    resumed:               "▶ 目標を再開しました: {goal}\nメッセージを送って続行するか、お待ちください — 次のターンで続きを進めます。"
      +    invalid:               "無効な目標: {error}"
      +    set:                   "⊙ 目標を設定しました ({budget} ターンの予算): {goal}\n目標が完了するか、一時停止/解除されるか、予算が尽きるまで作業を続けます。\nコントロール: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes コマンド**\n"
      +    skill_header:          "\n⚡ **スキルコマンド** ({count} 件アクティブ):"
      +    more_use_commands:     "\n... 他に {count} 件。完全なページ分けリストは `/commands` で確認してください。"
      +
      +  insights:
      +    invalid_days:          "--days の値が無効です: {value}"
      +    error:                 "インサイトの生成中にエラーが発生しました: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban エラー: {error}"
      +    subscribed_suffix:     "(購読しました — {task_id} が完了またはブロックされたときに通知されます)"
      +    truncated_suffix:      "… (切り詰めました; 完全な出力にはターミナルで `hermes kanban …` を使用してください)"
      +    no_output:             "(出力なし)"
      +
      +  personality:
      +    none_configured:       "`{path}/config.yaml` に人格が設定されていません"
      +    header:                "🎭 **利用可能な人格**\n"
      +    none_option:           "• `none` — (人格オーバーレイなし)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\n使い方: `/personality <name>`"
      +    save_failed:           "⚠️ 人格変更の保存に失敗しました: {error}"
      +    cleared:               "🎭 人格をクリアしました — 基本のエージェント動作を使用します。\n_(次のメッセージから有効)_"
      +    set_to:                "🎭 人格を **{name}** に設定しました\n_(次のメッセージから有効)_"
      +    unknown:               "不明な人格: `{name}`\n\n利用可能: {available}"
      +
      +  profile:
      +    header:                "👤 **プロファイル:** `{profile}`"
      +    home:                  "📂 **ホーム:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (デフォルト)"
      +    level_disabled:            "none (無効)"
      +    scope_session:             "セッションのオーバーライド"
      +    scope_global:              "グローバル設定"
      +    status:                    "🧠 **推論設定**\n\n**強度:** `{level}`\n**スコープ:** {scope}\n**表示:** {display}\n\n_使い方:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "オン ✓"
      +    display_off:               "オフ"
      +    display_set_on:            "🧠 ✓ 推論表示: **オン**\n**{platform}** 上で各応答の前にモデルの思考が表示されます。"
      +    display_set_off:           "🧠 ✓ **{platform}** での推論表示: **オフ**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` はサポートされていません。グローバルのデフォルトを変更するには `/reasoning <level> --global` を使用してください。"
      +    reset_done:                "🧠 ✓ セッションの推論オーバーライドをクリアしました。グローバル設定にフォールバックします。"
      +    unknown_arg:               "⚠️ 不明な引数: `{arg}`\n\n**有効なレベル:** none, minimal, low, medium, high, xhigh\n**表示:** show, hide\n**永続化:** セッションを越えて保存するには `--global` を追加"
      +    set_global:                "🧠 ✓ 推論強度を `{effort}` に設定しました (設定に保存)\n_(次のメッセージから有効)_"
      +    set_global_save_failed:    "🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 設定の保存に失敗)\n_(次のメッセージから有効)_"
      +    set_session:               "🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 永続化するには `--global` を追加)\n_(次のメッセージから有効)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp をキャンセルしました。MCP ツールは変更されていません。"
      +    always_followup:       "ℹ️ 今後の `/reload-mcp` は確認なしで実行されます。`config.yaml` で `approvals.mcp_reload_confirm: true` を設定すると再有効化できます。"
      +    confirm_prompt:        "⚠️ **/reload-mcp の確認**\n\nMCP サーバーを再読み込みすると、このセッションのツールセットが再構築され、**プロバイダーのプロンプトキャッシュが無効化されます** — 次のメッセージで完全な入力トークンが再送信されます。長コンテキストや高推論モデルではコストが高くなる可能性があります。\n\n選択してください:\n• **一度だけ承認** — 今すぐ再読み込み\n• **常に承認** — 今すぐ再読み込みし、このプロンプトを永続的に非表示\n• **キャンセル** — MCP ツールを変更しない\n\n_テキスト代替: `/approve`、`/always`、または `/cancel` と返信してください。_"
      +    header:                "🔄 **MCP サーバーを再読み込みしました**\n"
      +    reconnected:           "♻️ 再接続: {names}"
      +    added:                 "➕ 追加: {names}"
      +    removed:               "➖ 削除: {names}"
      +    none_connected:        "接続中の MCP サーバーはありません。"
      +    tools_available:       "\n🔧 {servers} 台のサーバーから {tools} 個のツールが利用可能"
      +    failed:                "❌ MCP の再読み込みに失敗しました: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **スキルを再読み込みしました**\n"
      +    no_new:                "新しいスキルは検出されませんでした。"
      +    total:                 "\n📚 {count} 個のスキルが利用可能"
      +    added_header:          "➕ **追加されたスキル:**"
      +    removed_header:        "➖ **削除されたスキル:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ スキルの再読み込みに失敗しました: {error}"
      +
      +  reset:
      +    header_default:        "✨ セッションをリセットしました。新たに開始します。"
      +    header_new:            "✨ 新しいセッションを開始しました。"
      +    header_titled:         "✨ 新しいセッションを開始しました: {title}"
      +    title_rejected:        "\n⚠️ タイトルが拒否されました: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — タイトルなしでセッションを開始しました。"
      +    title_empty_untitled:  "\n⚠️ クリーンアップ後にタイトルが空になりました — タイトルなしでセッションを開始しました。"
      +    tip:                   "\n✦ ヒント: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ ゲートウェイの再起動はすでに進行中です..."
      +    restarting:            "♻ ゲートウェイを再起動しています。60 秒以内に通知が届かない場合は、コンソールで `hermes gateway restart` を実行してください。"
      +
      +  resume:
      +    db_unavailable:        "セッションデータベースは利用できません。"
      +    no_named_sessions:     "名前付きセッションが見つかりません。\n`/title セッション名` で現在のセッションに名前を付けると、後で `/resume セッション名` で戻れます。"
      +    list_header:           "📋 **名前付きセッション**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\n使い方: `/resume <セッション名>`"
      +    list_failed:           "セッションを一覧表示できませんでした: {error}"
      +    not_found:             "'**{name}**' に一致するセッションが見つかりません。\n引数なしで `/resume` を実行すると利用可能なセッションを表示します。"
      +    already_on:            "📌 既にセッション **{name}** にいます。"
      +    switch_failed:         "セッションの切り替えに失敗しました。"
      +    resumed_one:           "↻ セッション **{title}** を再開しました ({count} メッセージ)。会話を復元しました。"
      +    resumed_many:          "↻ セッション **{title}** を再開しました ({count} メッセージ)。会話を復元しました。"
      +    resumed_no_count:      "↻ セッション **{title}** を再開しました。会話を復元しました。"
      +
      +  retry:
      +    no_previous:           "再試行する前のメッセージがありません。"
      +
      +  rollback:
      +    not_enabled:           "チェックポイントは有効になっていません。\nconfig.yaml で有効にしてください:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "{cwd} のチェックポイントが見つかりません"
      +    invalid_number:        "無効なチェックポイント番号です。1-{max} を使用してください。"
      +    restored:              "✅ チェックポイント {hash} に復元しました: {reason}\nロールバック前のスナップショットが自動的に保存されました。"
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "ホームチャンネルを保存できませんでした: {error}"
      +    success:               "✅ ホームチャンネルを **{name}** (ID: {chat_id}) に設定しました。\nCron ジョブとプラットフォーム間メッセージはここに配信されます。"
      +
      +  status:
      +    header:                "📊 **Hermes ゲートウェイ状態**"
      +    session_id:            "**セッション ID:** `{session_id}`"
      +    title:                 "**タイトル:** {title}"
      +    created:               "**作成日時:** {timestamp}"
      +    last_activity:         "**最終アクティビティ:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**エージェント実行中:** {state}"
      +    state_yes:             "はい ⚡"
      +    state_no:              "いいえ"
      +    queued:                "**キュー内の後続:** {count}"
      +    platforms:             "**接続プラットフォーム:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ 停止しました。エージェントはまだ開始していません — このセッションを続行できます。"
      +    stopped:               "⚡ 停止しました。このセッションを続行できます。"
      +    no_active:             "停止できるアクティブなタスクはありません。"
      +
      +  title:
      +    db_unavailable:        "セッションデータベースは利用できません。"
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ クリーンアップ後にタイトルが空になりました。印字可能な文字を使用してください。"
      +    set_to:                "✏️ セッションタイトルを設定しました: **{title}**"
      +    not_found:             "データベースにセッションが見つかりません。"
      +    current_with_title:    "📌 セッション: `{session_id}`\nタイトル: **{title}**"
      +    current_no_title:      "📌 セッション: `{session_id}`\nタイトル未設定。使い方: `/title セッション名`"
      +
      +  topic:
      +    not_telegram_dm:         "/topic コマンドは Telegram のプライベートチャットでのみ利用できます。"
      +    no_session_db:           "セッションデータベースを利用できません。"
      +    unauthorized:            "この bot で /topic を使用する権限がありません。"
      +    restore_needs_topic:     "セッションを復元するには、まず Telegram topic を作成または開いてから、その topic 内で /topic <session-id> を送信してください。新しい topic を作成するには、All Messages を開いて任意のメッセージを送信してください。"
      +    topics_disabled:         "この bot ではまだ Telegram topics が有効になっていません。\n\n有効にする方法:\n1. @BotFather を開きます。\n2. 自分の bot を選びます。\n3. Bot Settings → Threads Settings を開きます。\n4. Threaded Mode をオンにし、ユーザーが新しいスレッドを作成できるように設定します。\n\nそして /topic をもう一度送信してください。"
      +    topics_user_disallowed:  "Telegram topics は有効ですが、ユーザーは topic を作成できません。\n\n@BotFather → 自分の bot → Bot Settings → Threads Settings を開き、'Disallow users to create new threads' をオフにしてください。\n\nそして /topic をもう一度送信してください。"
      +    enable_failed:           "Telegram topic モードの有効化に失敗しました: {error}"
      +    bound_status:            "この topic は次にリンクされています:\nセッション: {label}\nID: {session_id}\n\nこの topic を新しいセッションに置き換えるには /new を使用してください。\n並行作業には、All Messages を開いてメッセージを送信し、別の topic を作成してください。"
      +    thread_ready:            "Telegram のマルチセッション topics が有効です。\n\nこの topic は独立した Hermes セッションとして使用されます。この topic の現在のセッションを置き換えるには /new を使用してください。並行作業には、All Messages を開いてメッセージを送信し、別の topic を作成してください。"
      +    untitled_session:        "無題のセッション"
      +
      +  undo:
      +    nothing:               "元に戻せる操作がありません。"
      +    removed:               "↩️ {count} 件のメッセージを取り消しました。\n削除: 「{preview}」"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update はメッセージングプラットフォームでのみ利用可能です。ターミナルで `hermes update` を実行してください。"
      +    not_git_repo:            "✗ Git リポジトリではありません — 更新できません。"
      +    hermes_cmd_not_found:    "✗ `hermes` コマンドが見つかりません。Hermes は実行中ですが、更新コマンドは PATH 上にも現在の Python インタープリタ経由でも実行可能ファイルを見つけられませんでした。ターミナルで `hermes update` を手動で実行してみてください。"
      +    start_failed:            "✗ 更新の開始に失敗しました: {error}"
      +    starting:                "⚕ Hermes の更新を開始しています… 進捗をここにストリーミングします。"
      +
      +  usage:
      +    rate_limits:              "⏱️ **レート制限:** {state}"
      +    header_session:           "📊 **セッショントークン使用状況**"
      +    label_model:              "モデル: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] {count}"
      +    label_cache_read:         "キャッシュ読み取りトークン: {count}"
      +    label_cache_write:        "キャッシュ書き込みトークン: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] {count}"
      +    label_total:              "合計: {count}"
      +    label_api_calls:          "API 呼び出し: {count}"
      +    label_cost:               "コスト: {prefix}${amount}"
      +    label_cost_included:      "コスト: 含まれています"
      +    label_context:            "コンテキスト: {used} / {total} ({pct}%)"
      +    label_compressions:       "圧縮回数: {count}"
      +    header_session_info:      "📊 **セッション情報**"
      +    label_messages:           "メッセージ数: {count}"
      +    label_estimated_context:  "推定コンテキスト: ~{count} トークン"
      +    detailed_after_first:     "_(詳細な使用状況は最初のエージェント応答後に利用可能)_"
      +    no_data:                  "このセッションの使用データはありません。"
      +
      +  verbose:
      +    not_enabled:           "`/verbose` コマンドはメッセージングプラットフォームで有効になっていません。\n\n`config.yaml` で有効にしてください:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ ツール進捗: **OFF** — ツールの動作は表示されません。"
      +    mode_new:              "⚙️ ツール進捗: **NEW** — ツールが変わったときに表示 (プレビュー長: `display.tool_preview_length`、デフォルト 40)。"
      +    mode_all:              "⚙️ ツール進捗: **ALL** — すべてのツール呼び出しを表示 (プレビュー長: `display.tool_preview_length`、デフォルト 40)。"
      +    mode_verbose:          "⚙️ ツール進捗: **VERBOSE** — すべてのツール呼び出しを完全な引数とともに表示。"
      +    saved_suffix:          "_(**{platform}** に保存しました — 次のメッセージから有効)_"
      +    save_failed:           "_(設定に保存できませんでした: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "音声モードを有効にしました。\n音声メッセージを送ると音声で返信します。\nすべてのメッセージへの音声返信は /voice tts を使ってください。"
      +    disabled_text:         "音声モードを無効にしました。テキストのみで返信します。"
      +    tts_enabled:           "自動 TTS を有効にしました。\nすべての返信に音声メッセージが含まれます。"
      +    status_mode:           "音声モード: {label}"
      +    status_channel:        "音声チャンネル: #{channel}"
      +    status_participants:   "参加者: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (発話中)"
      +    enabled_short:         "音声モードを有効にしました。"
      +    disabled_short:        "音声モードを無効にしました。"
      +    label_off:             "オフ (テキストのみ)"
      +    label_voice_only:      "オン (音声メッセージにのみ音声で返信)"
      +    label_all:             "TTS (すべてのメッセージに音声で返信)"
      +
      +  yolo:
      +    disabled:                   "⚠️ このセッションの YOLO モードは **OFF** — 危険なコマンドには承認が必要です。"
      +    enabled:                    "⚡ このセッションの YOLO モードは **ON** — すべてのコマンドが自動承認されます。注意して使用してください。"
      +
      +  shared:
      +    session_db_unavailable:      "セッションデータベースが利用できません。"
      +    session_db_unavailable_prefix: "セッションデータベースが利用できません"
      +    session_not_found:           "データベースにセッションが見つかりません。"
      +    warn_passthrough:            "⚠️ {error}"
    • locales/ko.yaml
      @@ -0,0 +1,350 @@
      +# Hermes 정적 메시지 카탈로그 -- 한국어
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  위험한 명령: {description}"
      +  choose_long:     "      [o]한 번  |  [s]세션  |  [a]항상  |  [d]거부"
      +  choose_short:    "      [o]한 번  |  [s]세션  |  [d]거부"
      +  prompt_long:     "      선택 [o/s/a/D]: "
      +  prompt_short:    "      선택 [o/s/D]: "
      +  timeout:         "      ⏱ 시간 초과 - 명령을 거부합니다"
      +  allowed_once:    "      ✓ 한 번 허용됨"
      +  allowed_session: "      ✓ 이 세션에서 허용됨"
      +  allowed_always:  "      ✓ 영구 허용 목록에 추가됨"
      +  denied:          "      ✗ 거부됨"
      +  cancelled:       "      ✗ 취소됨"
      +  blocklist_message: "이 명령은 무조건 차단 목록에 있으며 승인할 수 없습니다."
      +
      +gateway:
      +  approval_expired: "⚠️ 승인이 만료되었습니다 (에이전트가 더 이상 대기하지 않습니다). 에이전트에게 다시 시도하도록 요청하세요."
      +  draining:         "⏳ 재시작 전에 활성 에이전트 {count}명을 정리하는 중..."
      +  goal_cleared:     "✓ 목표가 삭제되었습니다."
      +  no_active_goal:   "활성 목표가 없습니다."
      +  config_read_failed: "⚠️ config.yaml을 읽을 수 없습니다: {error}"
      +  config_save_failed: "⚠️ 설정을 저장할 수 없습니다: {error}"
      +
      +  model:
      +    error_prefix:           "오류: {error}"
      +    switched:               "모델이 `{model}`(으)로 전환되었습니다"
      +    provider_label:         "제공자: {provider}"
      +    context_label:          "컨텍스트: {tokens} 토큰"
      +    max_output_label:       "최대 출력: {tokens} 토큰"
      +    cost_label:             "비용: {cost}"
      +    capabilities_label:     "기능: {capabilities}"
      +    prompt_caching_enabled: "프롬프트 캐싱: 활성화됨"
      +    warning_prefix:         "경고: {warning}"
      +    saved_global:           "config.yaml에 저장됨 (`--global`)"
      +    session_only_hint:      "_(세션 한정 — 영구 저장하려면 `--global`을 추가하세요)_"
      +    current_label:          "현재: `{model}` ({provider})"
      +    current_tag:            " (현재)"
      +    more_models_suffix:     " (+{count}개 더 있음)"
      +    usage_switch_model:     "`/model <name>` — 모델 전환"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — 제공자 전환"
      +    usage_persist:          "`/model <name> --global` — 영구 저장"
      +
      +  agents:
      +    header:                "🤖 **활성 에이전트 및 작업**"
      +    active_agents:         "**활성 에이전트:** {count}"
      +    this_chat:             " · 이 채팅"
      +    more:                  "... 외 {count}개 더"
      +    running_processes:     "**실행 중인 백그라운드 프로세스:** {count}"
      +    async_jobs:            "**게이트웨이 비동기 작업:** {count}"
      +    none:                  "활성 에이전트나 실행 중인 작업이 없습니다."
      +    state_starting:        "시작 중"
      +    state_running:         "실행 중"
      +
      +  approve:
      +    no_pending:            "승인 대기 중인 명령이 없습니다."
      +    once_singular:         "✅ 명령이 승인되었습니다. 에이전트가 재개됩니다..."
      +    once_plural:           "✅ 명령이 승인되었습니다 ({count}개). 에이전트가 재개됩니다..."
      +    session_singular:      "✅ 명령이 승인되었습니다 (이 세션 동안 패턴 승인됨). 에이전트가 재개됩니다..."
      +    session_plural:        "✅ 명령이 승인되었습니다 (이 세션 동안 패턴 승인됨) ({count}개). 에이전트가 재개됩니다..."
      +    always_singular:       "✅ 명령이 승인되었습니다 (패턴 영구 승인됨). 에이전트가 재개됩니다..."
      +    always_plural:         "✅ 명령이 승인되었습니다 (패턴 영구 승인됨) ({count}개). 에이전트가 재개됩니다..."
      +
      +  background:
      +    usage:                 "사용법: /background <prompt>\n예시: /background 오늘 HN 인기 글을 요약해줘\n\n프롬프트를 별도 세션에서 실행합니다. 계속 대화할 수 있으며, 완료되면 결과가 여기에 표시됩니다."
      +    started:               "🔄 백그라운드 작업이 시작되었습니다: \"{preview}\"\n작업 ID: {task_id}\n계속 대화하실 수 있습니다 — 완료되면 결과가 여기에 표시됩니다."
      +
      +  branch:
      +    db_unavailable:        "세션 데이터베이스를 사용할 수 없습니다."
      +    no_conversation:       "분기할 대화가 없습니다 — 먼저 메시지를 보내주세요."
      +    create_failed:         "분기 생성에 실패했습니다: {error}"
      +    switch_failed:         "분기는 생성되었으나 전환에 실패했습니다."
      +    branched_one:          "⑂ **{title}**(으)로 분기했습니다 (메시지 {count}개 복사됨)\n원본: `{parent}`\n분기: `{new}`\n원본으로 돌아가려면 `/resume`을 사용하세요."
      +    branched_many:         "⑂ **{title}**(으)로 분기했습니다 (메시지 {count}개 복사됨)\n원본: `{parent}`\n분기: `{new}`\n원본으로 돌아가려면 `/resume`을 사용하세요."
      +
      +  commands:
      +    usage:                 "사용법: `/commands [page]`"
      +    skill_header:          "⚡ **스킬 명령**:"
      +    default_desc:          "스킬 명령"
      +    none:                  "사용 가능한 명령이 없습니다."
      +    header:                "📚 **명령 목록** (총 {total}개, {page}/{total_pages} 페이지)"
      +    nav_prev:              "`/commands {page}` ← 이전"
      +    nav_next:              "다음 → `/commands {page}`"
      +    out_of_range:          "_(요청한 페이지 {requested}이(가) 범위를 벗어났습니다. {page} 페이지를 표시합니다.)_"
      +
      +  compress:
      +    not_enough:            "압축할 대화가 충분하지 않습니다 (최소 4개의 메시지가 필요합니다)."
      +    no_provider:           "구성된 제공자가 없습니다 -- 압축할 수 없습니다."
      +    nothing_to_do:         "아직 압축할 내용이 없습니다 (대화 내용이 모두 보호된 컨텍스트입니다)."
      +    focus_line:            "초점: \"{topic}\""
      +    summary_failed:        "⚠️ 요약 생성에 실패했습니다 ({error}). 과거 메시지 {count}개가 제거되어 자리표시자로 대체되었으며, 이전 컨텍스트는 더 이상 복구할 수 없습니다. auxiliary.compression 모델 설정을 확인해 보세요."
      +    aux_failed:            "ℹ️ 구성된 압축 모델 `{model}`이(가) 실패했습니다 ({error}). 메인 모델로 복구되어 컨텍스트는 보존되었지만, config.yaml의 `auxiliary.compression.model` 설정을 확인하는 것이 좋습니다."
      +    failed:                "압축 실패: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ 디버그 보고서 업로드 실패: {error}"
      +    header:                "**디버그 보고서가 업로드되었습니다:**"
      +    auto_delete:           "⏱ 페이스트는 6시간 후 자동 삭제됩니다."
      +    full_logs_hint:        "전체 로그 업로드는 CLI에서 `hermes debug share`를 사용하세요."
      +    share_hint:            "지원을 받으려면 이 링크를 Hermes 팀과 공유하세요."
      +
      +  deny:
      +    stale:                 "❌ 명령이 거부되었습니다 (승인이 만료됨)."
      +    no_pending:            "거부 대기 중인 명령이 없습니다."
      +    denied_singular:       "❌ 명령이 거부되었습니다."
      +    denied_plural:         "❌ 명령이 거부되었습니다 ({count}개)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast는 Priority Processing을 지원하는 OpenAI 모델에서만 사용할 수 있습니다."
      +    status:                "⚡ Priority Processing\n\n현재 모드: `{mode}`\n\n_사용법:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ 알 수 없는 인수: `{arg}`\n\n**유효한 옵션:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (설정에 저장됨)\n_(다음 메시지부터 적용됩니다)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (이 세션에만 적용)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 런타임 푸터: **{state}**\n필드: `{fields}`\n플랫폼: `{platform}`"
      +    usage:                 "사용법: `/footer [on|off|status]`"
      +    saved:                 "📎 런타임 푸터: **{state}**{example}\n_(전역 저장됨 — 다음 메시지부터 적용됩니다)_"
      +    example_line:          "\n예시: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "이 세션에서는 목표 기능을 사용할 수 없습니다."
      +    no_goal_set:           "설정된 목표가 없습니다."
      +    paused:                "⏸ 목표 일시정지: {goal}"
      +    no_resume:             "재개할 목표가 없습니다."
      +    resumed:               "▶ 목표 재개: {goal}\n메시지를 보내 계속하거나 기다려 주세요 — 다음 차례에 다음 단계를 진행하겠습니다."
      +    invalid:               "잘못된 목표: {error}"
      +    set:                   "⊙ 목표 설정됨 ({budget}회 예산): {goal}\n목표가 완료되거나, 일시정지/삭제하거나, 예산이 소진될 때까지 계속 작업하겠습니다.\n제어: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes 명령**\n"
      +    skill_header:          "\n⚡ **스킬 명령** ({count}개 활성):"
      +    more_use_commands:     "\n... 외 {count}개 더. 전체 목록은 `/commands`로 확인하세요."
      +
      +  insights:
      +    invalid_days:          "잘못된 --days 값: {value}"
      +    error:                 "인사이트 생성 중 오류: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban 오류: {error}"
      +    subscribed_suffix:     "(구독 중 — {task_id}이(가) 완료되거나 차단되면 알림을 받습니다)"
      +    truncated_suffix:      "… (잘림; 전체 출력을 보려면 터미널에서 `hermes kanban …`을 사용하세요)"
      +    no_output:             "(출력 없음)"
      +
      +  personality:
      +    none_configured:       "`{path}/config.yaml`에 구성된 성격이 없습니다"
      +    header:                "🎭 **사용 가능한 성격**\n"
      +    none_option:           "• `none` — (성격 오버레이 없음)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\n사용법: `/personality <name>`"
      +    save_failed:           "⚠️ 성격 변경 저장에 실패했습니다: {error}"
      +    cleared:               "🎭 성격이 해제되었습니다 — 기본 에이전트 동작을 사용합니다.\n_(다음 메시지부터 적용됩니다)_"
      +    set_to:                "🎭 성격이 **{name}**(으)로 설정되었습니다\n_(다음 메시지부터 적용됩니다)_"
      +    unknown:               "알 수 없는 성격: `{name}`\n\n사용 가능: {available}"
      +
      +  profile:
      +    header:                "👤 **프로필:** `{profile}`"
      +    home:                  "📂 **홈:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (기본값)"
      +    level_disabled:            "none (비활성화됨)"
      +    scope_session:             "세션 재정의"
      +    scope_global:              "전역 설정"
      +    status:                    "🧠 **추론 설정**\n\n**노력:** `{level}`\n**범위:** {scope}\n**표시:** {display}\n\n_사용법:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "켜짐 ✓"
      +    display_off:               "꺼짐"
      +    display_set_on:            "🧠 ✓ 추론 표시: **켜짐**\n**{platform}**에서 응답 전에 모델의 사고 과정이 표시됩니다."
      +    display_set_off:           "🧠 ✓ 추론 표시: **꺼짐** (**{platform}**에서)"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global`은 지원되지 않습니다. 전역 기본값을 변경하려면 `/reasoning <level> --global`을 사용하세요."
      +    reset_done:                "🧠 ✓ 세션 추론 재정의가 해제되었습니다. 전역 설정으로 돌아갑니다."
      +    unknown_arg:               "⚠️ 알 수 없는 인수: `{arg}`\n\n**유효한 수준:** none, minimal, low, medium, high, xhigh\n**표시:** show, hide\n**영구화:** 이 세션을 넘어 저장하려면 `--global`을 추가하세요"
      +    set_global:                "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (설정에 저장됨)\n_(다음 메시지부터 적용됩니다)_"
      +    set_global_save_failed:    "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 설정 저장 실패)\n_(다음 메시지부터 적용됩니다)_"
      +    set_session:               "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 영구 저장하려면 `--global` 추가)\n_(다음 메시지부터 적용됩니다)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp가 취소되었습니다. MCP 도구는 변경되지 않았습니다."
      +    always_followup:       "ℹ️ 이후 `/reload-mcp` 호출은 확인 없이 실행됩니다. config.yaml의 `approvals.mcp_reload_confirm: true`로 다시 활성화할 수 있습니다."
      +    confirm_prompt:        "⚠️ **/reload-mcp 확인**\n\nMCP 서버를 재로드하면 이 세션의 도구 세트가 재구성되며 **제공자 프롬프트 캐시가 무효화됩니다** — 다음 메시지에서 전체 입력 토큰이 다시 전송됩니다. 긴 컨텍스트 또는 고도 추론 모델에서는 비용이 클 수 있습니다.\n\n선택하세요:\n• **한 번 승인** — 지금 재로드\n• **항상 승인** — 지금 재로드하고 이 프롬프트를 영구 비활성화\n• **취소** — MCP 도구를 변경하지 않음\n\n_텍스트 대체: `/approve`, `/always`, `/cancel`로 응답하세요._"
      +    header:                "🔄 **MCP 서버가 재로드되었습니다**\n"
      +    reconnected:           "♻️ 재연결됨: {names}"
      +    added:                 "➕ 추가됨: {names}"
      +    removed:               "➖ 제거됨: {names}"
      +    none_connected:        "연결된 MCP 서버가 없습니다."
      +    tools_available:       "\n🔧 {servers}개 서버에서 {tools}개 도구 사용 가능"
      +    failed:                "❌ MCP 재로드 실패: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **스킬이 재로드되었습니다**\n"
      +    no_new:                "새로운 스킬이 감지되지 않았습니다."
      +    total:                 "\n📚 {count}개 스킬 사용 가능"
      +    added_header:          "➕ **추가된 스킬:**"
      +    removed_header:        "➖ **제거된 스킬:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ 스킬 재로드 실패: {error}"
      +
      +  reset:
      +    header_default:        "✨ 세션이 초기화되었습니다! 새로 시작합니다."
      +    header_new:            "✨ 새 세션이 시작되었습니다!"
      +    header_titled:         "✨ 새 세션이 시작되었습니다: {title}"
      +    title_rejected:        "\n⚠️ 제목이 거부되었습니다: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — 제목 없이 세션을 시작했습니다."
      +    title_empty_untitled:  "\n⚠️ 정리 후 제목이 비어 있습니다 — 제목 없이 세션을 시작했습니다."
      +    tip:                   "\n✦ 팁: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ 게이트웨이 재시작이 이미 진행 중입니다..."
      +    restarting:            "♻ 게이트웨이를 재시작 중입니다. 60초 이내에 알림이 오지 않으면 콘솔에서 `hermes gateway restart`로 재시작하세요."
      +
      +  resume:
      +    db_unavailable:        "세션 데이터베이스를 사용할 수 없습니다."
      +    no_named_sessions:     "이름이 지정된 세션이 없습니다.\n현재 세션에 이름을 지정하려면 `/title 내 세션`을 사용하고, 나중에 `/resume 내 세션`으로 돌아오세요."
      +    list_header:           "📋 **이름이 지정된 세션**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\n사용법: `/resume <session name>`"
      +    list_failed:           "세션 목록을 가져올 수 없습니다: {error}"
      +    not_found:             "'**{name}**'와 일치하는 세션이 없습니다.\n사용 가능한 세션을 보려면 인수 없이 `/resume`을 사용하세요."
      +    already_on:            "📌 이미 **{name}** 세션에 있습니다."
      +    switch_failed:         "세션 전환에 실패했습니다."
      +    resumed_one:           "↻ **{title}** 세션 재개됨 (메시지 {count}개). 대화가 복원되었습니다."
      +    resumed_many:          "↻ **{title}** 세션 재개됨 (메시지 {count}개). 대화가 복원되었습니다."
      +    resumed_no_count:      "↻ **{title}** 세션 재개됨. 대화가 복원되었습니다."
      +
      +  retry:
      +    no_previous:           "재시도할 이전 메시지가 없습니다."
      +
      +  rollback:
      +    not_enabled:           "체크포인트가 활성화되어 있지 않습니다.\nconfig.yaml에서 활성화하세요:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "{cwd}에 체크포인트를 찾을 수 없습니다"
      +    invalid_number:        "잘못된 체크포인트 번호입니다. 1-{max}을 사용하세요."
      +    restored:              "✅ 체크포인트 {hash}(으)로 복원됨: {reason}\n롤백 전 스냅샷이 자동으로 저장되었습니다."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "홈 채널 저장에 실패했습니다: {error}"
      +    success:               "✅ 홈 채널이 **{name}**(ID: {chat_id})(으)로 설정되었습니다.\n크론 작업과 플랫폼 간 메시지가 여기로 전달됩니다."
      +
      +  status:
      +    header:                "📊 **Hermes 게이트웨이 상태**"
      +    session_id:            "**세션 ID:** `{session_id}`"
      +    title:                 "**제목:** {title}"
      +    created:               "**생성됨:** {timestamp}"
      +    last_activity:         "**최종 활동:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**에이전트 실행 중:** {state}"
      +    state_yes:             "예 ⚡"
      +    state_no:              "아니오"
      +    queued:                "**대기 중인 후속 작업:** {count}"
      +    platforms:             "**연결된 플랫폼:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ 중지되었습니다. 에이전트가 아직 시작되지 않았습니다 — 이 세션을 계속할 수 있습니다."
      +    stopped:               "⚡ 중지되었습니다. 이 세션을 계속할 수 있습니다."
      +    no_active:             "중지할 활성 작업이 없습니다."
      +
      +  title:
      +    db_unavailable:        "세션 데이터베이스를 사용할 수 없습니다."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ 정리 후 제목이 비어 있습니다. 인쇄 가능한 문자를 사용해 주세요."
      +    set_to:                "✏️ 세션 제목 설정됨: **{title}**"
      +    not_found:             "데이터베이스에서 세션을 찾을 수 없습니다."
      +    current_with_title:    "📌 세션: `{session_id}`\n제목: **{title}**"
      +    current_no_title:      "📌 세션: `{session_id}`\n제목이 설정되지 않았습니다. 사용법: `/title 내 세션 이름`"
      +
      +  topic:
      +    not_telegram_dm:         "/topic 명령은 Telegram 비공개 채팅에서만 사용할 수 있습니다."
      +    no_session_db:           "세션 데이터베이스를 사용할 수 없습니다."
      +    unauthorized:            "이 봇에서 /topic을 사용할 권한이 없습니다."
      +    restore_needs_topic:     "세션을 복원하려면 먼저 Telegram 토픽을 만들거나 열고, 해당 토픽 안에서 /topic <session-id>를 보내세요. 새 토픽을 만들려면 All Messages를 열고 그곳으로 메시지를 보내세요."
      +    topics_disabled:         "이 봇에는 아직 Telegram 토픽이 활성화되어 있지 않습니다.\n\n활성화 방법:\n1. @BotFather를 엽니다.\n2. 봇을 선택합니다.\n3. Bot Settings → Threads Settings를 엽니다.\n4. Threaded Mode를 켜고 사용자가 새 스레드를 만들 수 있도록 허용합니다.\n\n그런 다음 다시 /topic을 보내세요."
      +    topics_user_disallowed:  "Telegram 토픽이 활성화되어 있지만, 사용자가 토픽을 만들 수 없습니다.\n\n@BotFather → 봇 선택 → Bot Settings → Threads Settings를 열고 'Disallow users to create new threads'를 끄세요.\n\n그런 다음 다시 /topic을 보내세요."
      +    enable_failed:           "Telegram 토픽 모드 활성화에 실패했습니다: {error}"
      +    bound_status:            "이 토픽은 다음에 연결되어 있습니다:\n세션: {label}\nID: {session_id}\n\n이 토픽을 새 세션으로 교체하려면 /new를 사용하세요.\n병렬 작업을 위해서는 All Messages를 열고 메시지를 보내 다른 토픽을 만드세요."
      +    thread_ready:            "Telegram 다중 세션 토픽이 활성화되었습니다.\n\n이 토픽은 독립된 Hermes 세션으로 사용됩니다. 이 토픽의 현재 세션을 교체하려면 /new를 사용하세요. 병렬 작업을 위해서는 All Messages를 열고 메시지를 보내 다른 토픽을 만드세요."
      +    untitled_session:        "제목 없는 세션"
      +
      +  undo:
      +    nothing:               "되돌릴 내용이 없습니다."
      +    removed:               "↩️ 메시지 {count}개를 되돌렸습니다.\n제거됨: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update는 메시징 플랫폼에서만 사용할 수 있습니다. 터미널에서 `hermes update`를 실행하세요."
      +    not_git_repo:            "✗ git 저장소가 아닙니다 — 업데이트할 수 없습니다."
      +    hermes_cmd_not_found:    "✗ `hermes` 명령을 찾을 수 없습니다. Hermes는 실행 중이지만 PATH나 현재 Python 인터프리터를 통해 실행 파일을 찾을 수 없습니다. 터미널에서 `hermes update`를 직접 실행해 보세요."
      +    start_failed:            "✗ 업데이트 시작 실패: {error}"
      +    starting:                "⚕ Hermes 업데이트를 시작합니다… 진행 상황을 여기에 스트리밍하겠습니다."
      +
      +  usage:
      +    rate_limits:              "⏱️ **요청 제한:** {state}"
      +    header_session:           "📊 **세션 토큰 사용량**"
      +    label_model:              "모델: `{model}`"
      +    label_input_tokens:       "입력 토큰: {count}"
      +    label_cache_read:         "캐시 읽기 토큰: {count}"
      +    label_cache_write:        "캐시 쓰기 토큰: {count}"
      +    label_output_tokens:      "출력 토큰: {count}"
      +    label_total:              "합계: {count}"
      +    label_api_calls:          "API 호출: {count}"
      +    label_cost:               "비용: {prefix}${amount}"
      +    label_cost_included:      "비용: 포함됨"
      +    label_context:            "컨텍스트: {used} / {total} ({pct}%)"
      +    label_compressions:       "압축: {count}"
      +    header_session_info:      "📊 **세션 정보**"
      +    label_messages:           "메시지: {count}"
      +    label_estimated_context:  "예상 컨텍스트: 약 {count} 토큰"
      +    detailed_after_first:     "_(자세한 사용량은 첫 에이전트 응답 이후 확인할 수 있습니다)_"
      +    no_data:                  "이 세션에 사용 가능한 사용량 데이터가 없습니다."
      +
      +  verbose:
      +    not_enabled:           "`/verbose` 명령은 메시징 플랫폼에서 활성화되어 있지 않습니다.\n\n`config.yaml`에서 활성화하세요:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ 도구 진행 상황: **OFF** — 도구 활동이 표시되지 않습니다."
      +    mode_new:              "⚙️ 도구 진행 상황: **NEW** — 도구가 변경될 때 표시됩니다 (미리보기 길이: `display.tool_preview_length`, 기본 40)."
      +    mode_all:              "⚙️ 도구 진행 상황: **ALL** — 모든 도구 호출이 표시됩니다 (미리보기 길이: `display.tool_preview_length`, 기본 40)."
      +    mode_verbose:          "⚙️ 도구 진행 상황: **VERBOSE** — 모든 도구 호출이 전체 인수와 함께 표시됩니다."
      +    saved_suffix:          "_(**{platform}**에 저장됨 — 다음 메시지부터 적용됩니다)_"
      +    save_failed:           "_(설정에 저장할 수 없습니다: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "음성 모드가 활성화되었습니다.\n음성 메시지를 보내시면 음성으로 답변하겠습니다.\n모든 메시지에 대해 음성으로 응답받으려면 /voice tts를 사용하세요."
      +    disabled_text:         "음성 모드가 비활성화되었습니다. 텍스트로만 응답합니다."
      +    tts_enabled:           "자동 TTS가 활성화되었습니다.\n모든 응답에 음성 메시지가 포함됩니다."
      +    status_mode:           "음성 모드: {label}"
      +    status_channel:        "음성 채널: #{channel}"
      +    status_participants:   "참가자: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (말하는 중)"
      +    enabled_short:         "음성 모드가 활성화되었습니다."
      +    disabled_short:        "음성 모드가 비활성화되었습니다."
      +    label_off:             "꺼짐 (텍스트 전용)"
      +    label_voice_only:      "켜짐 (음성 메시지에 음성으로 응답)"
      +    label_all:             "TTS (모든 메시지에 음성으로 응답)"
      +
      +  yolo:
      +    disabled:                   "⚠️ 이 세션에서 YOLO 모드 **꺼짐** — 위험한 명령은 승인이 필요합니다."
      +    enabled:                    "⚡ 이 세션에서 YOLO 모드 **켜짐** — 모든 명령이 자동 승인됩니다. 주의해서 사용하세요."
      +
      +  shared:
      +    session_db_unavailable:      "세션 데이터베이스를 사용할 수 없습니다."
      +    session_db_unavailable_prefix: "세션 데이터베이스를 사용할 수 없습니다"
      +    session_not_found:           "데이터베이스에서 세션을 찾을 수 없습니다."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/pt.yaml
      @@ -0,0 +1,350 @@
      +# Catálogo de mensagens estáticas do Hermes -- Português
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  COMANDO PERIGOSO: {description}"
      +  choose_long:     "      [o]uma vez  |  [s]sessão  |  [a]sempre  |  [d]negar"
      +  choose_short:    "      [o]uma vez  |  [s]sessão  |  [d]negar"
      +  prompt_long:     "      Escolha [o/s/a/D]: "
      +  prompt_short:    "      Escolha [o/s/D]: "
      +  timeout:         "      ⏱ Tempo esgotado — comando negado"
      +  allowed_once:    "      ✓ Permitido uma vez"
      +  allowed_session: "      ✓ Permitido nesta sessão"
      +  allowed_always:  "      ✓ Adicionado à lista de permissões permanente"
      +  denied:          "      ✗ Negado"
      +  cancelled:       "      ✗ Cancelado"
      +  blocklist_message: "Este comando está na lista de bloqueio incondicional e não pode ser aprovado."
      +
      +gateway:
      +  approval_expired: "⚠️ A aprovação expirou (o agente já não está à espera). Peça ao agente para tentar novamente."
      +  draining:         "⏳ A aguardar que {count} agente(s) ativo(s) terminem antes de reiniciar..."
      +  goal_cleared:     "✓ Objetivo removido."
      +  no_active_goal:   "Não há objetivo ativo."
      +  config_read_failed: "⚠️ Não foi possível ler config.yaml: {error}"
      +  config_save_failed: "⚠️ Não foi possível guardar a configuração: {error}"
      +
      +  model:
      +    error_prefix:           "Erro: {error}"
      +    switched:               "Modelo alterado para `{model}`"
      +    provider_label:         "Fornecedor: {provider}"
      +    context_label:          "Contexto: {tokens} tokens"
      +    max_output_label:       "Saída máxima: {tokens} tokens"
      +    cost_label:             "Custo: {cost}"
      +    capabilities_label:     "Capacidades: {capabilities}"
      +    prompt_caching_enabled: "Cache de prompts: ativado"
      +    warning_prefix:         "Aviso: {warning}"
      +    saved_global:           "Guardado em config.yaml (`--global`)"
      +    session_only_hint:      "_(apenas para esta sessão — adiciona `--global` para tornar permanente)_"
      +    current_label:          "Atual: `{model}` em {provider}"
      +    current_tag:            " (atual)"
      +    more_models_suffix:     " (+{count} mais)"
      +    usage_switch_model:     "`/model <name>` — mudar de modelo"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — mudar de fornecedor"
      +    usage_persist:          "`/model <name> --global` — guardar permanentemente"
      +
      +  agents:
      +    header:                "🤖 **Agentes e tarefas ativos**"
      +    active_agents:         "**Agentes ativos:** {count}"
      +    this_chat:             " · este chat"
      +    more:                  "... e mais {count}"
      +    running_processes:     "**Processos em segundo plano em execução:** {count}"
      +    async_jobs:            "**Tarefas assíncronas do gateway:** {count}"
      +    none:                  "Não há agentes ativos nem tarefas em execução."
      +    state_starting:        "a iniciar"
      +    state_running:         "em execução"
      +
      +  approve:
      +    no_pending:            "Não há nenhum comando pendente para aprovar."
      +    once_singular:         "✅ Comando aprovado. O agente está a retomar..."
      +    once_plural:           "✅ Comandos aprovados ({count} comandos). O agente está a retomar..."
      +    session_singular:      "✅ Comando aprovado (padrão aprovado para esta sessão). O agente está a retomar..."
      +    session_plural:        "✅ Comandos aprovados (padrão aprovado para esta sessão) ({count} comandos). O agente está a retomar..."
      +    always_singular:       "✅ Comando aprovado (padrão aprovado permanentemente). O agente está a retomar..."
      +    always_plural:         "✅ Comandos aprovados (padrão aprovado permanentemente) ({count} comandos). O agente está a retomar..."
      +
      +  background:
      +    usage:                 "Uso: /background <prompt>\nExemplo: /background Resume as principais histórias do HN de hoje\n\nExecuta o prompt numa sessão separada. Podes continuar a conversar — o resultado aparecerá aqui quando estiver concluído."
      +    started:               "🔄 Tarefa em segundo plano iniciada: \"{preview}\"\nID da tarefa: {task_id}\nPodes continuar a conversar — os resultados aparecerão aqui quando estiverem prontos."
      +
      +  branch:
      +    db_unavailable:        "Base de dados de sessões indisponível."
      +    no_conversation:       "Não há conversa para ramificar — envia uma mensagem primeiro."
      +    create_failed:         "Falha ao criar ramo: {error}"
      +    switch_failed:         "Ramo criado, mas não foi possível mudar para ele."
      +    branched_one:          "⑂ Ramificado para **{title}** ({count} mensagem copiada)\nOriginal: `{parent}`\nRamo: `{new}`\nUsa `/resume` para voltar ao original."
      +    branched_many:         "⑂ Ramificado para **{title}** ({count} mensagens copiadas)\nOriginal: `{parent}`\nRamo: `{new}`\nUsa `/resume` para voltar ao original."
      +
      +  commands:
      +    usage:                 "Uso: `/commands [page]`"
      +    skill_header:          "⚡ **Comandos de skill**:"
      +    default_desc:          "Comando de skill"
      +    none:                  "Não há comandos disponíveis."
      +    header:                "📚 **Comandos** ({total} no total, página {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← anterior"
      +    nav_next:              "seguinte → `/commands {page}`"
      +    out_of_range:          "_(A página solicitada {requested} estava fora do intervalo, a mostrar a página {page}.)_"
      +
      +  compress:
      +    not_enough:            "Não há conversa suficiente para comprimir (são necessárias pelo menos 4 mensagens)."
      +    no_provider:           "Nenhum fornecedor configurado — não é possível comprimir."
      +    nothing_to_do:         "Ainda não há nada para comprimir (a transcrição continua a ser todo o contexto protegido)."
      +    focus_line:            "Foco: \"{topic}\""
      +    summary_failed:        "⚠️ Falha ao gerar o resumo ({error}). {count} mensagem(ns) histórica(s) foram removidas e substituídas por um marcador; o contexto anterior já não pode ser recuperado. Considera verificar a configuração do modelo auxiliary.compression."
      +    aux_failed:            "ℹ️ O modelo de compressão configurado `{model}` falhou ({error}). Recuperado com o teu modelo principal — o contexto está intacto — mas talvez queiras verificar `auxiliary.compression.model` em config.yaml."
      +    failed:                "Compressão falhou: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Falha ao carregar relatório de depuração: {error}"
      +    header:                "**Relatório de depuração carregado:**"
      +    auto_delete:           "⏱ Os pastes serão eliminados automaticamente em 6 horas."
      +    full_logs_hint:        "Para enviar logs completos, usa `hermes debug share` a partir da CLI."
      +    share_hint:            "Partilha estes links com a equipa do Hermes para obter suporte."
      +
      +  deny:
      +    stale:                 "❌ Comando negado (a aprovação tinha expirado)."
      +    no_pending:            "Não há nenhum comando pendente para negar."
      +    denied_singular:       "❌ Comando negado."
      +    denied_plural:         "❌ Comandos negados ({count} comandos)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast só está disponível para modelos da OpenAI que suportam Priority Processing."
      +    status:                "⚡ Priority Processing\n\nModo atual: `{mode}`\n\n_Uso:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Argumento desconhecido: `{arg}`\n\n**Opções válidas:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (guardado na configuração)\n_(produz efeito na próxima mensagem)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (apenas esta sessão)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Rodapé de execução: **{state}**\nCampos: `{fields}`\nPlataforma: `{platform}`"
      +    usage:                 "Uso: `/footer [on|off|status]`"
      +    saved:                 "📎 Rodapé de execução: **{state}**{example}\n_(guardado globalmente — produz efeito na próxima mensagem)_"
      +    example_line:          "\nExemplo: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Os objetivos não estão disponíveis nesta sessão."
      +    no_goal_set:           "Nenhum objetivo definido."
      +    paused:                "⏸ Objetivo pausado: {goal}"
      +    no_resume:             "Nenhum objetivo para retomar."
      +    resumed:               "▶ Objetivo retomado: {goal}\nEnvia qualquer mensagem para continuar, ou aguarda — darei o próximo passo no próximo turno."
      +    invalid:               "Objetivo inválido: {error}"
      +    set:                   "⊙ Objetivo definido (orçamento de {budget} turnos): {goal}\nVou continuar a trabalhar até o objetivo estar concluído, pausares/limpares ou o orçamento esgotar.\nControlos: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Comandos do Hermes**\n"
      +    skill_header:          "\n⚡ **Comandos de skill** ({count} ativos):"
      +    more_use_commands:     "\n... e mais {count}. Usa `/commands` para a lista paginada completa."
      +
      +  insights:
      +    invalid_days:          "Valor --days inválido: {value}"
      +    error:                 "Erro ao gerar análise: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ erro do kanban: {error}"
      +    subscribed_suffix:     "(subscrito — receberás uma notificação quando {task_id} terminar ou bloquear)"
      +    truncated_suffix:      "… (truncado; usa `hermes kanban …` no teu terminal para a saída completa)"
      +    no_output:             "(sem saída)"
      +
      +  personality:
      +    none_configured:       "Nenhuma personalidade configurada em `{path}/config.yaml`"
      +    header:                "🎭 **Personalidades disponíveis**\n"
      +    none_option:           "• `none` — (sem sobreposição de personalidade)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nUso: `/personality <name>`"
      +    save_failed:           "⚠️ Falha ao guardar a alteração de personalidade: {error}"
      +    cleared:               "🎭 Personalidade removida — a usar o comportamento base do agente.\n_(produz efeito na próxima mensagem)_"
      +    set_to:                "🎭 Personalidade definida como **{name}**\n_(produz efeito na próxima mensagem)_"
      +    unknown:               "Personalidade desconhecida: `{name}`\n\nDisponíveis: {available}"
      +
      +  profile:
      +    header:                "👤 **Perfil:** `{profile}`"
      +    home:                  "📂 **Início:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (predefinido)"
      +    level_disabled:            "none (desativado)"
      +    scope_session:             "substituição de sessão"
      +    scope_global:              "configuração global"
      +    status:                    "🧠 **Definições de raciocínio**\n\n**Esforço:** `{level}`\n**Âmbito:** {scope}\n**Visualização:** {display}\n\n_Uso:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "ativada ✓"
      +    display_off:               "desativada"
      +    display_set_on:            "🧠 ✓ Visualização do raciocínio: **ATIVADA**\nO pensamento do modelo será mostrado antes de cada resposta em **{platform}**."
      +    display_set_off:           "🧠 ✓ Visualização do raciocínio: **DESATIVADA** para **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` não é suportado. Usa `/reasoning <level> --global` para alterar o predefinido global."
      +    reset_done:                "🧠 ✓ Substituição de raciocínio da sessão removida; a regressar à configuração global."
      +    unknown_arg:               "⚠️ Argumento desconhecido: `{arg}`\n\n**Níveis válidos:** none, minimal, low, medium, high, xhigh\n**Visualização:** show, hide\n**Persistir:** adiciona `--global` para guardar para além desta sessão"
      +    set_global:                "🧠 ✓ Esforço de raciocínio definido como `{effort}` (guardado na configuração)\n_(produz efeito na próxima mensagem)_"
      +    set_global_save_failed:    "🧠 ✓ Esforço de raciocínio definido como `{effort}` (apenas sessão — falha ao guardar a configuração)\n_(produz efeito na próxima mensagem)_"
      +    set_session:               "🧠 ✓ Esforço de raciocínio definido como `{effort}` (apenas sessão — adiciona `--global` para persistir)\n_(produz efeito na próxima mensagem)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp cancelado. As ferramentas MCP não foram alteradas."
      +    always_followup:       "ℹ️ Próximas chamadas a `/reload-mcp` serão executadas sem confirmação. Reativa através de `approvals.mcp_reload_confirm: true` em `config.yaml`."
      +    confirm_prompt:        "⚠️ **Confirmar /reload-mcp**\n\nRecarregar os servidores MCP reconstrói o conjunto de ferramentas desta sessão e **invalida a cache de prompt do fornecedor** — a próxima mensagem reenviará os tokens de entrada completos. Em modelos de contexto longo ou de raciocínio elevado isto pode ser dispendioso.\n\nEscolhe:\n• **Aprovar uma vez** — recarregar agora\n• **Aprovar sempre** — recarregar agora e silenciar este pedido permanentemente\n• **Cancelar** — manter as ferramentas MCP inalteradas\n\n_Alternativa em texto: responde `/approve`, `/always` ou `/cancel`._"
      +    header:                "🔄 **Servidores MCP recarregados**\n"
      +    reconnected:           "♻️ Reconectados: {names}"
      +    added:                 "➕ Adicionados: {names}"
      +    removed:               "➖ Removidos: {names}"
      +    none_connected:        "Não há servidores MCP ligados."
      +    tools_available:       "\n🔧 {tools} ferramenta(s) disponíveis de {servers} servidor(es)"
      +    failed:                "❌ Falha ao recarregar MCP: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Skills recarregadas**\n"
      +    no_new:                "Não foram detetadas novas skills."
      +    total:                 "\n📚 {count} skill(s) disponíveis"
      +    added_header:          "➕ **Skills adicionadas:**"
      +    removed_header:        "➖ **Skills removidas:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Falha ao recarregar skills: {error}"
      +
      +  reset:
      +    header_default:        "✨ Sessão reiniciada! A começar do zero."
      +    header_new:            "✨ Nova sessão iniciada!"
      +    header_titled:         "✨ Nova sessão iniciada: {title}"
      +    title_rejected:        "\n⚠️ Título rejeitado: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — sessão iniciada sem título."
      +    title_empty_untitled:  "\n⚠️ O título fica vazio após a limpeza — sessão iniciada sem título."
      +    tip:                   "\n✦ Dica: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ O reinício do gateway já está em curso..."
      +    restarting:            "♻ A reiniciar o gateway. Se não fores notificado em 60 segundos, reinicia a partir da consola com `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "Base de dados de sessões indisponível."
      +    no_named_sessions:     "Não foram encontradas sessões com nome.\nUsa `/title A minha sessão` para nomear a sessão atual e depois `/resume A minha sessão` para voltar a ela."
      +    list_header:           "📋 **Sessões com nome**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nUso: `/resume <nome da sessão>`"
      +    list_failed:           "Não foi possível listar as sessões: {error}"
      +    not_found:             "Não foi encontrada nenhuma sessão correspondente a '**{name}**'.\nUsa `/resume` sem argumentos para ver as sessões disponíveis."
      +    already_on:            "📌 Já estás na sessão **{name}**."
      +    switch_failed:         "Falha ao mudar de sessão."
      +    resumed_one:           "↻ Sessão **{title}** retomada ({count} mensagem). Conversa restaurada."
      +    resumed_many:          "↻ Sessão **{title}** retomada ({count} mensagens). Conversa restaurada."
      +    resumed_no_count:      "↻ Sessão **{title}** retomada. Conversa restaurada."
      +
      +  retry:
      +    no_previous:           "Não há mensagem anterior para tentar novamente."
      +
      +  rollback:
      +    not_enabled:           "Os checkpoints não estão ativados.\nAtiva-os em config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Não foram encontrados checkpoints para {cwd}"
      +    invalid_number:        "Número de checkpoint inválido. Usa 1-{max}."
      +    restored:              "✅ Restaurado para o checkpoint {hash}: {reason}\nFoi guardado automaticamente um snapshot anterior ao rollback."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Falha ao guardar o canal principal: {error}"
      +    success:               "✅ Canal principal definido como **{name}** (ID: {chat_id}).\nAs tarefas cron e mensagens entre plataformas serão entregues aqui."
      +
      +  status:
      +    header:                "📊 **Estado do Hermes Gateway**"
      +    session_id:            "**ID da sessão:** `{session_id}`"
      +    title:                 "**Título:** {title}"
      +    created:               "**Criada:** {timestamp}"
      +    last_activity:         "**Última atividade:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Agente em execução:** {state}"
      +    state_yes:             "Sim ⚡"
      +    state_no:              "Não"
      +    queued:                "**Seguimentos em fila:** {count}"
      +    platforms:             "**Plataformas ligadas:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Parado. O agente ainda não tinha começado — podes continuar esta sessão."
      +    stopped:               "⚡ Parado. Podes continuar esta sessão."
      +    no_active:             "Não há nenhuma tarefa ativa para parar."
      +
      +  title:
      +    db_unavailable:        "Base de dados de sessões indisponível."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ O título está vazio após a limpeza. Usa caracteres imprimíveis."
      +    set_to:                "✏️ Título da sessão definido: **{title}**"
      +    not_found:             "Sessão não encontrada na base de dados."
      +    current_with_title:    "📌 Sessão: `{session_id}`\nTítulo: **{title}**"
      +    current_no_title:      "📌 Sessão: `{session_id}`\nSem título. Uso: `/title O meu nome de sessão`"
      +
      +  topic:
      +    not_telegram_dm:         "O comando /topic só está disponível em chats privados do Telegram."
      +    no_session_db:           "Base de dados de sessões indisponível."
      +    unauthorized:            "Não tens autorização para usar /topic neste bot."
      +    restore_needs_topic:     "Para restaurar uma sessão, cria ou abre primeiro um topic do Telegram, depois envia /topic <session-id> dentro desse topic. Para criar um novo topic, abre All Messages e envia qualquer mensagem aí."
      +    topics_disabled:         "Os topics do Telegram ainda não estão ativados para este bot.\n\nComo ativá-los:\n1. Abre @BotFather.\n2. Escolhe o teu bot.\n3. Abre Bot Settings → Threads Settings.\n4. Ativa Threaded Mode e garante que os utilizadores podem criar novas threads.\n\nDepois envia /topic novamente."
      +    topics_user_disallowed:  "Os topics do Telegram estão ativados, mas os utilizadores não podem criá-los.\n\nAbre @BotFather → escolhe o teu bot → Bot Settings → Threads Settings, depois desativa 'Disallow users to create new threads'.\n\nDepois envia /topic novamente."
      +    enable_failed:           "Falha ao ativar o modo topic do Telegram: {error}"
      +    bound_status:            "Este topic está associado a:\nSessão: {label}\nID: {session_id}\n\nUsa /new para substituir este topic por uma sessão nova.\nPara trabalho paralelo, abre All Messages e envia uma mensagem aí para criar outro topic."
      +    thread_ready:            "Os topics multi-sessão do Telegram estão ativados.\n\nEste topic será usado como uma sessão independente do Hermes. Usa /new para substituir a sessão atual deste topic. Para trabalho paralelo, abre All Messages e envia uma mensagem aí para criar outro topic."
      +    untitled_session:        "Sessão sem título"
      +
      +  undo:
      +    nothing:               "Nada para anular."
      +    removed:               "↩️ {count} mensagem(ns) anulada(s).\nRemovido: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update só está disponível em plataformas de mensagens. Executa `hermes update` a partir do terminal."
      +    not_git_repo:            "✗ Não é um repositório git — não é possível atualizar."
      +    hermes_cmd_not_found:    "✗ Não foi possível localizar o comando `hermes`. O Hermes está em execução, mas o comando de atualização não conseguiu encontrar o executável no PATH nem através do interpretador Python atual. Tenta executar `hermes update` manualmente no teu terminal."
      +    start_failed:            "✗ Falha ao iniciar a atualização: {error}"
      +    starting:                "⚕ A iniciar a atualização do Hermes… Vou transmitir o progresso aqui."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Limites de taxa:** {state}"
      +    header_session:           "📊 **Utilização de tokens da sessão**"
      +    label_model:              "Modelo: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] de entrada: {count}"
      +    label_cache_read:         "Tokens de leitura de cache: {count}"
      +    label_cache_write:        "Tokens de escrita de cache: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] de saída: {count}"
      +    label_total:              "Total: {count}"
      +    label_api_calls:          "Chamadas à API: {count}"
      +    label_cost:               "Custo: {prefix}${amount}"
      +    label_cost_included:      "Custo: incluído"
      +    label_context:            "Contexto: {used} / {total} ({pct}%)"
      +    label_compressions:       "Compressões: {count}"
      +    header_session_info:      "📊 **Informações da sessão**"
      +    label_messages:           "Mensagens: {count}"
      +    label_estimated_context:  "Contexto estimado: ~{count} tokens"
      +    detailed_after_first:     "_(Utilização detalhada disponível após a primeira resposta do agente)_"
      +    no_data:                  "Não há dados de utilização disponíveis para esta sessão."
      +
      +  verbose:
      +    not_enabled:           "O comando `/verbose` não está ativado para plataformas de mensagens.\n\nAtiva-o em `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Progresso de ferramentas: **OFF** — não é mostrada qualquer atividade de ferramentas."
      +    mode_new:              "⚙️ Progresso de ferramentas: **NEW** — mostrado quando a ferramenta muda (comprimento da pré-visualização: `display.tool_preview_length`, predefinição 40)."
      +    mode_all:              "⚙️ Progresso de ferramentas: **ALL** — cada chamada de ferramenta é mostrada (comprimento da pré-visualização: `display.tool_preview_length`, predefinição 40)."
      +    mode_verbose:          "⚙️ Progresso de ferramentas: **VERBOSE** — cada chamada de ferramenta com os argumentos completos."
      +    saved_suffix:          "_(guardado para **{platform}** — produz efeito na próxima mensagem)_"
      +    save_failed:           "_(não foi possível guardar na configuração: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Modo de voz ativado.\nResponderei com voz quando enviares mensagens de voz.\nUsa /voice tts para receber respostas de voz em todas as mensagens."
      +    disabled_text:         "Modo de voz desativado. Respostas apenas em texto."
      +    tts_enabled:           "Auto-TTS ativado.\nTodas as respostas incluirão uma mensagem de voz."
      +    status_mode:           "Modo de voz: {label}"
      +    status_channel:        "Canal de voz: #{channel}"
      +    status_participants:   "Participantes: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (a falar)"
      +    enabled_short:         "Modo de voz ativado."
      +    disabled_short:        "Modo de voz desativado."
      +    label_off:             "Desativado (apenas texto)"
      +    label_voice_only:      "Ativado (resposta de voz a mensagens de voz)"
      +    label_all:             "TTS (resposta de voz a todas as mensagens)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Modo YOLO **DESATIVADO** nesta sessão — comandos perigosos exigirão aprovação."
      +    enabled:                    "⚡ Modo YOLO **ATIVADO** nesta sessão — todos os comandos são aprovados automaticamente. Usa com precaução."
      +
      +  shared:
      +    session_db_unavailable:      "Base de dados de sessões indisponível."
      +    session_db_unavailable_prefix: "Base de dados de sessões indisponível"
      +    session_not_found:           "Sessão não encontrada na base de dados."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/ru.yaml
      @@ -0,0 +1,350 @@
      +# Каталог статических сообщений Hermes -- Русский
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  ОПАСНАЯ КОМАНДА: {description}"
      +  choose_long:     "      [o]один раз  |  [s]сеанс  |  [a]всегда  |  [d]отклонить"
      +  choose_short:    "      [o]один раз  |  [s]сеанс  |  [d]отклонить"
      +  prompt_long:     "      Выбор [o/s/a/D]: "
      +  prompt_short:    "      Выбор [o/s/D]: "
      +  timeout:         "      ⏱ Время ожидания истекло — команда отклонена"
      +  allowed_once:    "      ✓ Разрешено один раз"
      +  allowed_session: "      ✓ Разрешено для этого сеанса"
      +  allowed_always:  "      ✓ Добавлено в постоянный список разрешённых"
      +  denied:          "      ✗ Отклонено"
      +  cancelled:       "      ✗ Отменено"
      +  blocklist_message: "Эта команда находится в безусловном списке блокировки и не может быть одобрена."
      +
      +gateway:
      +  approval_expired: "⚠️ Срок одобрения истёк (агент больше не ожидает). Попросите агента повторить попытку."
      +  draining:         "⏳ Ожидание завершения {count} активных агент(ов) перед перезапуском..."
      +  goal_cleared:     "✓ Цель очищена."
      +  no_active_goal:   "Нет активной цели."
      +  config_read_failed: "⚠️ Не удалось прочитать config.yaml: {error}"
      +  config_save_failed: "⚠️ Не удалось сохранить конфигурацию: {error}"
      +
      +  model:
      +    error_prefix:           "Ошибка: {error}"
      +    switched:               "Модель изменена на `{model}`"
      +    provider_label:         "Провайдер: {provider}"
      +    context_label:          "Контекст: {tokens} токенов"
      +    max_output_label:       "Макс. вывод: {tokens} токенов"
      +    cost_label:             "Стоимость: {cost}"
      +    capabilities_label:     "Возможности: {capabilities}"
      +    prompt_caching_enabled: "Кеширование промптов: включено"
      +    warning_prefix:         "Предупреждение: {warning}"
      +    saved_global:           "Сохранено в config.yaml (`--global`)"
      +    session_only_hint:      "_(только для этого сеанса — добавьте `--global`, чтобы сохранить)_"
      +    current_label:          "Текущая: `{model}` на {provider}"
      +    current_tag:            " (текущая)"
      +    more_models_suffix:     " (+ещё {count})"
      +    usage_switch_model:     "`/model <name>` — сменить модель"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — сменить провайдера"
      +    usage_persist:          "`/model <name> --global` — сохранить навсегда"
      +
      +  agents:
      +    header:                "🤖 **Активные агенты и задачи**"
      +    active_agents:         "**Активные агенты:** {count}"
      +    this_chat:             " · этот чат"
      +    more:                  "... и ещё {count}"
      +    running_processes:     "**Выполняющиеся фоновые процессы:** {count}"
      +    async_jobs:            "**Асинхронные задачи шлюза:** {count}"
      +    none:                  "Нет активных агентов или выполняющихся задач."
      +    state_starting:        "запускается"
      +    state_running:         "выполняется"
      +
      +  approve:
      +    no_pending:            "Нет команды, ожидающей одобрения."
      +    once_singular:         "✅ Команда одобрена. Агент возобновляет работу..."
      +    once_plural:           "✅ Команды одобрены ({count} команд). Агент возобновляет работу..."
      +    session_singular:      "✅ Команда одобрена (шаблон одобрен для этого сеанса). Агент возобновляет работу..."
      +    session_plural:        "✅ Команды одобрены (шаблон одобрен для этого сеанса) ({count} команд). Агент возобновляет работу..."
      +    always_singular:       "✅ Команда одобрена (шаблон одобрен навсегда). Агент возобновляет работу..."
      +    always_plural:         "✅ Команды одобрены (шаблон одобрен навсегда) ({count} команд). Агент возобновляет работу..."
      +
      +  background:
      +    usage:                 "Использование: /background <запрос>\nПример: /background Сделай сводку лучших историй с HN сегодня\n\nЗапускает запрос в отдельном сеансе. Можно продолжить общение — результат появится здесь по завершении."
      +    started:               "🔄 Фоновая задача запущена: «{preview}»\nID задачи: {task_id}\nМожно продолжить общение — результаты появятся здесь по завершении."
      +
      +  branch:
      +    db_unavailable:        "База данных сеансов недоступна."
      +    no_conversation:       "Нет беседы для ответвления — сначала отправьте сообщение."
      +    create_failed:         "Не удалось создать ветку: {error}"
      +    switch_failed:         "Ветка создана, но переключиться на неё не удалось."
      +    branched_one:          "⑂ Создана ветка **{title}** (скопировано {count} сообщение)\nОригинал: `{parent}`\nВетка: `{new}`\nИспользуйте `/resume`, чтобы вернуться к оригиналу."
      +    branched_many:         "⑂ Создана ветка **{title}** (скопировано {count} сообщений)\nОригинал: `{parent}`\nВетка: `{new}`\nИспользуйте `/resume`, чтобы вернуться к оригиналу."
      +
      +  commands:
      +    usage:                 "Использование: `/commands [page]`"
      +    skill_header:          "⚡ **Команды навыков**:"
      +    default_desc:          "Команда навыка"
      +    none:                  "Нет доступных команд."
      +    header:                "📚 **Команды** (всего {total}, страница {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← пред."
      +    nav_next:              "след. → `/commands {page}`"
      +    out_of_range:          "_(Запрошенная страница {requested} вне диапазона, показана страница {page}.)_"
      +
      +  compress:
      +    not_enough:            "Недостаточно беседы для сжатия (нужно минимум 4 сообщения)."
      +    no_provider:           "Провайдер не настроен — сжатие невозможно."
      +    nothing_to_do:         "Пока нечего сжимать (стенограмма всё ещё полностью является защищённым контекстом)."
      +    focus_line:            "Фокус: \"{topic}\""
      +    summary_failed:        "⚠️ Не удалось сгенерировать сводку ({error}). {count} историч. сообщений было удалено и заменено заполнителем; предыдущий контекст больше нельзя восстановить. Проверьте конфигурацию модели auxiliary.compression."
      +    aux_failed:            "ℹ️ Настроенная модель сжатия `{model}` дала сбой ({error}). Восстановлено с помощью основной модели — контекст не повреждён — но рекомендуется проверить `auxiliary.compression.model` в config.yaml."
      +    failed:                "Сжатие не удалось: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Не удалось загрузить отчёт отладки: {error}"
      +    header:                "**Отчёт отладки загружен:**"
      +    auto_delete:           "⏱ Вставки автоматически удалятся через 6 часов."
      +    full_logs_hint:        "Для загрузки полных журналов используйте `hermes debug share` из CLI."
      +    share_hint:            "Поделитесь этими ссылками с командой Hermes для получения поддержки."
      +
      +  deny:
      +    stale:                 "❌ Команда отклонена (одобрение устарело)."
      +    no_pending:            "Нет команды для отклонения."
      +    denied_singular:       "❌ Команда отклонена."
      +    denied_plural:         "❌ Команды отклонены ({count} команд)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast доступен только для моделей OpenAI, поддерживающих Priority Processing."
      +    status:                "⚡ Priority Processing\n\nТекущий режим: `{mode}`\n\n_Использование:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Неизвестный аргумент: `{arg}`\n\n**Допустимые варианты:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (сохранено в конфигурации)\n_(вступит в силу со следующего сообщения)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (только этот сеанс)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Нижний колонтитул среды выполнения: **{state}**\nПоля: `{fields}`\nПлатформа: `{platform}`"
      +    usage:                 "Использование: `/footer [on|off|status]`"
      +    saved:                 "📎 Нижний колонтитул среды выполнения: **{state}**{example}\n_(сохранено глобально — вступит в силу со следующего сообщения)_"
      +    example_line:          "\nПример: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Цели недоступны в этом сеансе."
      +    no_goal_set:           "Цель не задана."
      +    paused:                "⏸ Цель приостановлена: {goal}"
      +    no_resume:             "Нет цели для возобновления."
      +    resumed:               "▶ Цель возобновлена: {goal}\nОтправьте любое сообщение, чтобы продолжить, или подождите — я сделаю следующий шаг на следующем ходу."
      +    invalid:               "Недопустимая цель: {error}"
      +    set:                   "⊙ Цель задана (бюджет {budget} ходов): {goal}\nЯ продолжу работу, пока цель не будет достигнута, вы её не приостановите/очистите, или бюджет не исчерпается.\nУправление: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Команды Hermes**\n"
      +    skill_header:          "\n⚡ **Команды навыков** (активных: {count}):"
      +    more_use_commands:     "\n... и ещё {count}. Используйте `/commands` для полного списка с постраничной разбивкой."
      +
      +  insights:
      +    invalid_days:          "Недействительное значение --days: {value}"
      +    error:                 "Ошибка при формировании аналитики: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ ошибка kanban: {error}"
      +    subscribed_suffix:     "(подписка оформлена — вы получите уведомление, когда {task_id} завершится или будет заблокирован)"
      +    truncated_suffix:      "… (сокращено; используйте `hermes kanban …` в терминале для полного вывода)"
      +    no_output:             "(нет вывода)"
      +
      +  personality:
      +    none_configured:       "В `{path}/config.yaml` не настроено ни одной личности"
      +    header:                "🎭 **Доступные личности**\n"
      +    none_option:           "• `none` — (без наложения личности)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nИспользование: `/personality <name>`"
      +    save_failed:           "⚠️ Не удалось сохранить изменение личности: {error}"
      +    cleared:               "🎭 Личность очищена — используется базовое поведение агента.\n_(вступит в силу со следующего сообщения)_"
      +    set_to:                "🎭 Личность установлена на **{name}**\n_(вступит в силу со следующего сообщения)_"
      +    unknown:               "Неизвестная личность: `{name}`\n\nДоступные: {available}"
      +
      +  profile:
      +    header:                "👤 **Профиль:** `{profile}`"
      +    home:                  "📂 **Домашний каталог:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (по умолчанию)"
      +    level_disabled:            "none (отключено)"
      +    scope_session:             "переопределение сеанса"
      +    scope_global:              "глобальная конфигурация"
      +    status:                    "🧠 **Настройки рассуждений**\n\n**Усилия:** `{level}`\n**Область:** {scope}\n**Отображение:** {display}\n\n_Использование:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "включено ✓"
      +    display_off:               "выключено"
      +    display_set_on:            "🧠 ✓ Отображение рассуждений: **ВКЛ.**\nМысли модели будут показываться перед каждым ответом на **{platform}**."
      +    display_set_off:           "🧠 ✓ Отображение рассуждений: **ВЫКЛ.** для **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` не поддерживается. Используйте `/reasoning <level> --global`, чтобы изменить глобальное значение по умолчанию."
      +    reset_done:                "🧠 ✓ Переопределение рассуждений для сеанса сброшено; возврат к глобальной конфигурации."
      +    unknown_arg:               "⚠️ Неизвестный аргумент: `{arg}`\n\n**Допустимые уровни:** none, minimal, low, medium, high, xhigh\n**Отображение:** show, hide\n**Сохранение:** добавьте `--global`, чтобы сохранить за пределами этого сеанса"
      +    set_global:                "🧠 ✓ Усилия рассуждений установлены на `{effort}` (сохранено в конфигурации)\n_(вступит в силу со следующего сообщения)_"
      +    set_global_save_failed:    "🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — не удалось сохранить конфигурацию)\n_(вступит в силу со следующего сообщения)_"
      +    set_session:               "🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — добавьте `--global`, чтобы сохранить)\n_(вступит в силу со следующего сообщения)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp отменено. MCP-инструменты без изменений."
      +    always_followup:       "ℹ️ Будущие вызовы `/reload-mcp` будут выполняться без подтверждения. Снова включить можно через `approvals.mcp_reload_confirm: true` в config.yaml."
      +    confirm_prompt:        "⚠️ **Подтверждение /reload-mcp**\n\nПерезагрузка MCP-серверов перестраивает набор инструментов для этого сеанса и **сбрасывает кеш промпта провайдера** — следующее сообщение повторно отправит все входные токены. На моделях с длинным контекстом или высоким уровнем рассуждений это может быть дорого.\n\nВыберите:\n• **Одобрить один раз** — перезагрузить сейчас\n• **Всегда одобрять** — перезагрузить и навсегда отключить этот запрос\n• **Отменить** — оставить MCP-инструменты без изменений\n\n_Текстовая альтернатива: ответьте `/approve`, `/always` или `/cancel`._"
      +    header:                "🔄 **MCP-серверы перезагружены**\n"
      +    reconnected:           "♻️ Переподключено: {names}"
      +    added:                 "➕ Добавлено: {names}"
      +    removed:               "➖ Удалено: {names}"
      +    none_connected:        "Нет подключённых MCP-серверов."
      +    tools_available:       "\n🔧 {tools} инструмент(ов) доступно с {servers} сервер(ов)"
      +    failed:                "❌ Ошибка перезагрузки MCP: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Навыки перезагружены**\n"
      +    no_new:                "Новых навыков не обнаружено."
      +    total:                 "\n📚 {count} навык(ов) доступно"
      +    added_header:          "➕ **Добавленные навыки:**"
      +    removed_header:        "➖ **Удалённые навыки:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Ошибка перезагрузки навыков: {error}"
      +
      +  reset:
      +    header_default:        "✨ Сеанс сброшен! Начинаем с чистого листа."
      +    header_new:            "✨ Новый сеанс запущен!"
      +    header_titled:         "✨ Новый сеанс запущен: {title}"
      +    title_rejected:        "\n⚠️ Название отклонено: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — сеанс запущен без названия."
      +    title_empty_untitled:  "\n⚠️ После очистки название пусто — сеанс запущен без названия."
      +    tip:                   "\n✦ Совет: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Перезапуск шлюза уже выполняется..."
      +    restarting:            "♻ Перезапуск шлюза. Если уведомление не придёт в течение 60 секунд, перезапустите из консоли командой `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "База данных сеансов недоступна."
      +    no_named_sessions:     "Именованных сеансов не найдено.\nИспользуйте `/title Мой сеанс`, чтобы назвать текущий сеанс, затем `/resume Мой сеанс`, чтобы вернуться к нему позже."
      +    list_header:           "📋 **Именованные сеансы**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nИспользование: `/resume <название сеанса>`"
      +    list_failed:           "Не удалось получить список сеансов: {error}"
      +    not_found:             "Сеанс, соответствующий '**{name}**', не найден.\nИспользуйте `/resume` без аргументов, чтобы увидеть доступные сеансы."
      +    already_on:            "📌 Уже в сеансе **{name}**."
      +    switch_failed:         "Не удалось переключить сеанс."
      +    resumed_one:           "↻ Сеанс **{title}** возобновлён ({count} сообщение). Беседа восстановлена."
      +    resumed_many:          "↻ Сеанс **{title}** возобновлён ({count} сообщений). Беседа восстановлена."
      +    resumed_no_count:      "↻ Сеанс **{title}** возобновлён. Беседа восстановлена."
      +
      +  retry:
      +    no_previous:           "Нет предыдущего сообщения для повтора."
      +
      +  rollback:
      +    not_enabled:           "Контрольные точки не включены.\nВключите в config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Контрольных точек для {cwd} не найдено"
      +    invalid_number:        "Недействительный номер контрольной точки. Используйте 1-{max}."
      +    restored:              "✅ Восстановлено до контрольной точки {hash}: {reason}\nСнимок перед откатом сохранён автоматически."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Не удалось сохранить главный канал: {error}"
      +    success:               "✅ Главный канал установлен на **{name}** (ID: {chat_id}).\nCron-задачи и межплатформенные сообщения будут доставляться сюда."
      +
      +  status:
      +    header:                "📊 **Состояние Hermes Gateway**"
      +    session_id:            "**ID сеанса:** `{session_id}`"
      +    title:                 "**Название:** {title}"
      +    created:               "**Создано:** {timestamp}"
      +    last_activity:         "**Последняя активность:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Агент активен:** {state}"
      +    state_yes:             "Да ⚡"
      +    state_no:              "Нет"
      +    queued:                "**Очередь продолжений:** {count}"
      +    platforms:             "**Подключённые платформы:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Остановлено. Агент ещё не начинал — вы можете продолжить этот сеанс."
      +    stopped:               "⚡ Остановлено. Вы можете продолжить этот сеанс."
      +    no_active:             "Нет активной задачи для остановки."
      +
      +  title:
      +    db_unavailable:        "База данных сеансов недоступна."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ После очистки название пусто. Используйте печатные символы."
      +    set_to:                "✏️ Название сеанса установлено: **{title}**"
      +    not_found:             "Сеанс не найден в базе данных."
      +    current_with_title:    "📌 Сеанс: `{session_id}`\nНазвание: **{title}**"
      +    current_no_title:      "📌 Сеанс: `{session_id}`\nНазвание не задано. Использование: `/title Название моего сеанса`"
      +
      +  topic:
      +    not_telegram_dm:         "Команда /topic доступна только в личных чатах Telegram."
      +    no_session_db:           "База данных сеансов недоступна."
      +    unauthorized:            "У вас нет прав использовать /topic в этом боте."
      +    restore_needs_topic:     "Чтобы восстановить сеанс, сначала создайте или откройте Telegram topic, затем отправьте /topic <session-id> в этом topic. Чтобы создать новый topic, откройте All Messages и отправьте там любое сообщение."
      +    topics_disabled:         "Telegram topics ещё не включены для этого бота.\n\nКак включить:\n1. Откройте @BotFather.\n2. Выберите своего бота.\n3. Откройте Bot Settings → Threads Settings.\n4. Включите Threaded Mode и убедитесь, что пользователям разрешено создавать новые threads.\n\nЗатем снова отправьте /topic."
      +    topics_user_disallowed:  "Telegram topics включены, но пользователям не разрешено создавать topics.\n\nОткройте @BotFather → выберите своего бота → Bot Settings → Threads Settings, затем выключите 'Disallow users to create new threads'.\n\nЗатем снова отправьте /topic."
      +    enable_failed:           "Не удалось включить режим Telegram topic: {error}"
      +    bound_status:            "Этот topic привязан к:\nСеанс: {label}\nID: {session_id}\n\nИспользуйте /new, чтобы заменить этот topic новым сеансом.\nДля параллельной работы откройте All Messages и отправьте там сообщение, чтобы создать другой topic."
      +    thread_ready:            "Многосеансовые Telegram topics включены.\n\nЭтот topic будет использоваться как независимый сеанс Hermes. Используйте /new, чтобы заменить текущий сеанс этого topic. Для параллельной работы откройте All Messages и отправьте там сообщение, чтобы создать другой topic."
      +    untitled_session:        "Сеанс без названия"
      +
      +  undo:
      +    nothing:               "Нечего отменять."
      +    removed:               "↩️ Отменено сообщений: {count}.\nУдалено: «{preview}»"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update доступен только на платформах обмена сообщениями. Выполните `hermes update` в терминале."
      +    not_git_repo:            "✗ Не git-репозиторий — обновление невозможно."
      +    hermes_cmd_not_found:    "✗ Не удалось найти команду `hermes`. Hermes запущен, но команда обновления не нашла исполняемый файл в PATH или через текущий интерпретатор Python. Попробуйте выполнить `hermes update` вручную в терминале."
      +    start_failed:            "✗ Не удалось запустить обновление: {error}"
      +    starting:                "⚕ Запуск обновления Hermes… Я буду транслировать прогресс сюда."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Ограничения скорости:** {state}"
      +    header_session:           "📊 **Использование токенов сеанса**"
      +    label_model:              "Модель: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] токены: {count}"
      +    label_cache_read:         "Токены чтения кеша: {count}"
      +    label_cache_write:        "Токены записи кеша: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] токены: {count}"
      +    label_total:              "Всего: {count}"
      +    label_api_calls:          "Вызовы API: {count}"
      +    label_cost:               "Стоимость: {prefix}${amount}"
      +    label_cost_included:      "Стоимость: включено"
      +    label_context:            "Контекст: {used} / {total} ({pct}%)"
      +    label_compressions:       "Сжатий: {count}"
      +    header_session_info:      "📊 **Информация о сеансе**"
      +    label_messages:           "Сообщений: {count}"
      +    label_estimated_context:  "Ориентировочный контекст: ~{count} токенов"
      +    detailed_after_first:     "_(Подробное использование доступно после первого ответа агента)_"
      +    no_data:                  "Данные об использовании для этого сеанса отсутствуют."
      +
      +  verbose:
      +    not_enabled:           "Команда `/verbose` не включена для платформ обмена сообщениями.\n\nВключите в `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Прогресс инструментов: **OFF** — активность инструментов не показывается."
      +    mode_new:              "⚙️ Прогресс инструментов: **NEW** — показывается при смене инструмента (длина предпросмотра: `display.tool_preview_length`, по умолчанию 40)."
      +    mode_all:              "⚙️ Прогресс инструментов: **ALL** — показывается каждый вызов инструмента (длина предпросмотра: `display.tool_preview_length`, по умолчанию 40)."
      +    mode_verbose:          "⚙️ Прогресс инструментов: **VERBOSE** — каждый вызов инструмента с полными аргументами."
      +    saved_suffix:          "_(сохранено для **{platform}** — вступит в силу со следующего сообщения)_"
      +    save_failed:           "_(не удалось сохранить в конфигурацию: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Голосовой режим включён.\nЯ буду отвечать голосом, когда вы отправляете голосовые сообщения.\nИспользуйте /voice tts, чтобы получать голосовые ответы на все сообщения."
      +    disabled_text:         "Голосовой режим отключён. Только текстовые ответы."
      +    tts_enabled:           "Авто-TTS включён.\nВсе ответы будут содержать голосовое сообщение."
      +    status_mode:           "Голосовой режим: {label}"
      +    status_channel:        "Голосовой канал: #{channel}"
      +    status_participants:   "Участники: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (говорит)"
      +    enabled_short:         "Голосовой режим включён."
      +    disabled_short:        "Голосовой режим отключён."
      +    label_off:             "Выкл. (только текст)"
      +    label_voice_only:      "Вкл. (голосовой ответ на голосовые сообщения)"
      +    label_all:             "TTS (голосовой ответ на все сообщения)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Режим YOLO для этого сеанса **ОТКЛЮЧЁН** — опасные команды потребуют одобрения."
      +    enabled:                    "⚡ Режим YOLO для этого сеанса **ВКЛЮЧЁН** — все команды одобряются автоматически. Используйте с осторожностью."
      +
      +  shared:
      +    session_db_unavailable:      "База данных сеансов недоступна."
      +    session_db_unavailable_prefix: "База данных сеансов недоступна"
      +    session_not_found:           "Сеанс не найден в базе данных."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/tr.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "Aktif hedef yok."
         config_read_failed: "⚠️ config.yaml okunamadı: {error}"
         config_save_failed: "⚠️ Yapılandırma kaydedilemedi: {error}"
      +
      +  model:
      +    error_prefix:           "Hata: {error}"
      +    switched:               "Model `{model}` olarak değiştirildi"
      +    provider_label:         "Sağlayıcı: {provider}"
      +    context_label:          "Bağlam: {tokens} token"
      +    max_output_label:       "Maks. çıktı: {tokens} token"
      +    cost_label:             "Maliyet: {cost}"
      +    capabilities_label:     "Yetenekler: {capabilities}"
      +    prompt_caching_enabled: "Prompt önbelleği: etkin"
      +    warning_prefix:         "Uyarı: {warning}"
      +    saved_global:           "config.yaml'a kaydedildi (`--global`)"
      +    session_only_hint:      "_(yalnızca bu oturum — kalıcı yapmak için `--global` ekleyin)_"
      +    current_label:          "Geçerli: `{model}` ({provider})"
      +    current_tag:            " (geçerli)"
      +    more_models_suffix:     " (+{count} tane daha)"
      +    usage_switch_model:     "`/model <name>` — modeli değiştir"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — sağlayıcıyı değiştir"
      +    usage_persist:          "`/model <name> --global` — kalıcı kaydet"
      +
      +  agents:
      +    header:                "🤖 **Aktif Ajanlar ve Görevler**"
      +    active_agents:         "**Aktif ajanlar:** {count}"
      +    this_chat:             " · bu sohbet"
      +    more:                  "... ve {count} tane daha"
      +    running_processes:     "**Çalışan arka plan süreçleri:** {count}"
      +    async_jobs:            "**Gateway asenkron işleri:** {count}"
      +    none:                  "Aktif ajan veya çalışan görev yok."
      +    state_starting:        "başlatılıyor"
      +    state_running:         "çalışıyor"
      +
      +  approve:
      +    no_pending:            "Onaylanacak bekleyen komut yok."
      +    once_singular:         "✅ Komut onaylandı. Ajan devam ediyor..."
      +    once_plural:           "✅ Komutlar onaylandı ({count} komut). Ajan devam ediyor..."
      +    session_singular:      "✅ Komut onaylandı (desen bu oturum için onaylandı). Ajan devam ediyor..."
      +    session_plural:        "✅ Komutlar onaylandı (desen bu oturum için onaylandı) ({count} komut). Ajan devam ediyor..."
      +    always_singular:       "✅ Komut onaylandı (desen kalıcı olarak onaylandı). Ajan devam ediyor..."
      +    always_plural:         "✅ Komutlar onaylandı (desen kalıcı olarak onaylandı) ({count} komut). Ajan devam ediyor..."
      +
      +  background:
      +    usage:                 "Kullanım: /background <prompt>\nÖrnek: /background Bugünün öne çıkan HN haberlerini özetle\n\nİstemi ayrı bir oturumda çalıştırır. Sohbete devam edebilirsin — sonuç tamamlandığında burada görünecek."
      +    started:               "🔄 Arka plan görevi başlatıldı: \"{preview}\"\nGörev kimliği: {task_id}\nSohbete devam edebilirsin — sonuçlar tamamlandığında burada görünecek."
      +
      +  branch:
      +    db_unavailable:        "Oturum veritabanı kullanılamıyor."
      +    no_conversation:       "Dallandırılacak konuşma yok — önce bir mesaj gönderin."
      +    create_failed:         "Dal oluşturulamadı: {error}"
      +    switch_failed:         "Dal oluşturuldu ancak ona geçilemedi."
      +    branched_one:          "⑂ **{title}** dalına geçildi ({count} mesaj kopyalandı)\nOrijinal: `{parent}`\nDal: `{new}`\nOrijinale geri dönmek için `/resume` kullanın."
      +    branched_many:         "⑂ **{title}** dalına geçildi ({count} mesaj kopyalandı)\nOrijinal: `{parent}`\nDal: `{new}`\nOrijinale geri dönmek için `/resume` kullanın."
      +
      +  commands:
      +    usage:                 "Kullanım: `/commands [page]`"
      +    skill_header:          "⚡ **Skill Komutları**:"
      +    default_desc:          "Skill komutu"
      +    none:                  "Kullanılabilir komut yok."
      +    header:                "📚 **Komutlar** (toplam {total}, sayfa {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← önceki"
      +    nav_next:              "sonraki → `/commands {page}`"
      +    out_of_range:          "_(İstenen sayfa {requested} aralık dışındaydı, sayfa {page} gösteriliyor.)_"
      +
      +  compress:
      +    not_enough:            "Sıkıştırmak için yeterli konuşma yok (en az 4 mesaj gerekli)."
      +    no_provider:           "Yapılandırılmış sağlayıcı yok — sıkıştırılamıyor."
      +    nothing_to_do:         "Henüz sıkıştırılacak bir şey yok (transkript hâlâ tamamen korunan bağlam)."
      +    focus_line:            "Odak: \"{topic}\""
      +    summary_failed:        "⚠️ Özet oluşturma başarısız ({error}). {count} geçmiş mesaj kaldırılıp yer tutucuyla değiştirildi; önceki bağlam artık kurtarılamaz. auxiliary.compression model yapılandırmanızı kontrol edin."
      +    aux_failed:            "ℹ️ Yapılandırılmış sıkıştırma modeli `{model}` başarısız oldu ({error}). Ana modelinizle kurtarıldı — bağlam sağlam — ancak config.yaml içindeki `auxiliary.compression.model` öğesini kontrol etmek isteyebilirsiniz."
      +    failed:                "Sıkıştırma başarısız: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Hata ayıklama raporu yüklenemedi: {error}"
      +    header:                "**Hata ayıklama raporu yüklendi:**"
      +    auto_delete:           "⏱ Paste'ler 6 saat içinde otomatik olarak silinecek."
      +    full_logs_hint:        "Tam günlük yüklemeleri için CLI'dan `hermes debug share` kullanın."
      +    share_hint:            "Destek için bu bağlantıları Hermes ekibiyle paylaşın."
      +
      +  deny:
      +    stale:                 "❌ Komut reddedildi (onay geçersizdi)."
      +    no_pending:            "Reddedilecek bekleyen komut yok."
      +    denied_singular:       "❌ Komut reddedildi."
      +    denied_plural:         "❌ Komutlar reddedildi ({count} komut)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast yalnızca Priority Processing destekleyen OpenAI modellerinde kullanılabilir."
      +    status:                "⚡ Priority Processing\n\nMevcut mod: `{mode}`\n\n_Kullanım:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Bilinmeyen argüman: `{arg}`\n\n**Geçerli seçenekler:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (yapılandırmaya kaydedildi)\n_(sonraki mesajda geçerli olur)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (yalnızca bu oturum)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Çalışma zamanı altbilgisi: **{state}**\nAlanlar: `{fields}`\nPlatform: `{platform}`"
      +    usage:                 "Kullanım: `/footer [on|off|status]`"
      +    saved:                 "📎 Çalışma zamanı altbilgisi: **{state}**{example}\n_(genel olarak kaydedildi — sonraki mesajda geçerli olur)_"
      +    example_line:          "\nÖrnek: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Bu oturumda hedefler kullanılamıyor."
      +    no_goal_set:           "Hedef ayarlanmadı."
      +    paused:                "⏸ Hedef duraklatıldı: {goal}"
      +    no_resume:             "Devam ettirilecek hedef yok."
      +    resumed:               "▶ Hedef devam ettirildi: {goal}\nDevam etmek için herhangi bir mesaj gönderin veya bekleyin — bir sonraki turda adımı atacağım."
      +    invalid:               "Geçersiz hedef: {error}"
      +    set:                   "⊙ Hedef ayarlandı ({budget} turluk bütçe): {goal}\nHedef tamamlanana, siz duraklatana/temizleyene veya bütçe tükenene kadar çalışmaya devam edeceğim.\nKontroller: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes Komutları**\n"
      +    skill_header:          "\n⚡ **Skill Komutları** ({count} aktif):"
      +    more_use_commands:     "\n... ve {count} tane daha. Tam sayfalı liste için `/commands` kullanın."
      +
      +  insights:
      +    invalid_days:          "Geçersiz --days değeri: {value}"
      +    error:                 "Analiz oluşturulurken hata: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban hatası: {error}"
      +    subscribed_suffix:     "(abone olundu — {task_id} tamamlandığında veya engellendiğinde bildirim alacaksınız)"
      +    truncated_suffix:      "… (kısaltıldı; tam çıktı için terminalinizde `hermes kanban …` komutunu kullanın)"
      +    no_output:             "(çıktı yok)"
      +
      +  personality:
      +    none_configured:       "`{path}/config.yaml` içinde yapılandırılmış kişilik yok"
      +    header:                "🎭 **Mevcut Kişilikler**\n"
      +    none_option:           "• `none` — (kişilik kaplaması yok)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nKullanım: `/personality <name>`"
      +    save_failed:           "⚠️ Kişilik değişikliği kaydedilemedi: {error}"
      +    cleared:               "🎭 Kişilik temizlendi — temel ajan davranışı kullanılıyor.\n_(bir sonraki mesajda etkili olur)_"
      +    set_to:                "🎭 Kişilik **{name}** olarak ayarlandı\n_(bir sonraki mesajda etkili olur)_"
      +    unknown:               "Bilinmeyen kişilik: `{name}`\n\nMevcut: {available}"
      +
      +  profile:
      +    header:                "👤 **Profil:** `{profile}`"
      +    home:                  "📂 **Ana dizin:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (varsayılan)"
      +    level_disabled:            "none (devre dışı)"
      +    scope_session:             "oturum geçersiz kılma"
      +    scope_global:              "genel yapılandırma"
      +    status:                    "🧠 **Akıl Yürütme Ayarları**\n\n**Güç:** `{level}`\n**Kapsam:** {scope}\n**Görüntüleme:** {display}\n\n_Kullanım:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "açık ✓"
      +    display_off:               "kapalı"
      +    display_set_on:            "🧠 ✓ Akıl yürütme görüntüleme: **AÇIK**\n**{platform}** üzerinde her yanıttan önce modelin düşüncesi gösterilecek."
      +    display_set_off:           "🧠 ✓ **{platform}** için akıl yürütme görüntüleme: **KAPALI**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` desteklenmiyor. Genel varsayılanı değiştirmek için `/reasoning <level> --global` kullanın."
      +    reset_done:                "🧠 ✓ Oturumun akıl yürütme geçersiz kılması temizlendi; genel yapılandırmaya geri dönülüyor."
      +    unknown_arg:               "⚠️ Bilinmeyen argüman: `{arg}`\n\n**Geçerli seviyeler:** none, minimal, low, medium, high, xhigh\n**Görüntüleme:** show, hide\n**Kalıcı:** bu oturumun ötesinde kaydetmek için `--global` ekleyin"
      +    set_global:                "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yapılandırmaya kaydedildi)\n_(sonraki mesajda etkili)_"
      +    set_global_save_failed:    "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yalnızca bu oturum — yapılandırma kaydedilemedi)\n_(sonraki mesajda etkili)_"
      +    set_session:               "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yalnızca bu oturum — kalıcı yapmak için `--global` ekleyin)\n_(sonraki mesajda etkili)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp iptal edildi. MCP araçları değiştirilmedi."
      +    always_followup:       "ℹ️ Bundan sonraki `/reload-mcp` çağrıları onaysız çalışacak. `config.yaml` içinde `approvals.mcp_reload_confirm: true` ile yeniden etkinleştirebilirsiniz."
      +    confirm_prompt:        "⚠️ **/reload-mcp Onayı**\n\nMCP sunucularını yeniden yüklemek bu oturumdaki araç kümesini yeniden oluşturur ve **sağlayıcı prompt önbelleğini geçersiz kılar** — bir sonraki mesaj tüm giriş token'larını yeniden gönderir. Uzun bağlam veya yüksek akıl yürütmeli modellerde bu maliyetli olabilir.\n\nSeçim yapın:\n• **Bir Kez Onayla** — şimdi yeniden yükle\n• **Her Zaman Onayla** — şimdi yeniden yükle ve bu onayı kalıcı olarak sustur\n• **İptal** — MCP araçlarını değiştirme\n\n_Metin alternatifi: `/approve`, `/always` veya `/cancel` ile yanıtlayın._"
      +    header:                "🔄 **MCP Sunucuları Yeniden Yüklendi**\n"
      +    reconnected:           "♻️ Yeniden bağlanan: {names}"
      +    added:                 "➕ Eklenen: {names}"
      +    removed:               "➖ Kaldırılan: {names}"
      +    none_connected:        "Bağlı MCP sunucusu yok."
      +    tools_available:       "\n🔧 {servers} sunucudan {tools} araç kullanılabilir"
      +    failed:                "❌ MCP yeniden yükleme başarısız: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Beceriler Yeniden Yüklendi**\n"
      +    no_new:                "Yeni beceri tespit edilmedi."
      +    total:                 "\n📚 {count} beceri kullanılabilir"
      +    added_header:          "➕ **Eklenen Beceriler:**"
      +    removed_header:        "➖ **Kaldırılan Beceriler:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Beceri yeniden yükleme başarısız: {error}"
      +
      +  reset:
      +    header_default:        "✨ Oturum sıfırlandı! Yeniden başlıyoruz."
      +    header_new:            "✨ Yeni oturum başlatıldı!"
      +    header_titled:         "✨ Yeni oturum başlatıldı: {title}"
      +    title_rejected:        "\n⚠️ Başlık reddedildi: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — oturum başlıksız başlatıldı."
      +    title_empty_untitled:  "\n⚠️ Temizlik sonrası başlık boş — oturum başlıksız başlatıldı."
      +    tip:                   "\n✦ İpucu: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Gateway yeniden başlatma zaten sürüyor..."
      +    restarting:            "♻ Gateway yeniden başlatılıyor. 60 saniye içinde bildirim almazsanız konsoldan `hermes gateway restart` ile yeniden başlatın."
      +
      +  resume:
      +    db_unavailable:        "Oturum veritabanı kullanılamıyor."
      +    no_named_sessions:     "Adlandırılmış oturum bulunamadı.\nMevcut oturumu adlandırmak için `/title Oturumum`, daha sonra geri dönmek için `/resume Oturumum` kullanın."
      +    list_header:           "📋 **Adlandırılmış Oturumlar**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nKullanım: `/resume <oturum adı>`"
      +    list_failed:           "Oturumlar listelenemedi: {error}"
      +    not_found:             "'**{name}**' ile eşleşen oturum bulunamadı.\nKullanılabilir oturumları görmek için argümansız `/resume` kullanın."
      +    already_on:            "📌 Zaten **{name}** oturumundasınız."
      +    switch_failed:         "Oturum değiştirilemedi."
      +    resumed_one:           "↻ **{title}** oturumu sürdürüldü ({count} mesaj). Konuşma geri yüklendi."
      +    resumed_many:          "↻ **{title}** oturumu sürdürüldü ({count} mesaj). Konuşma geri yüklendi."
      +    resumed_no_count:      "↻ **{title}** oturumu sürdürüldü. Konuşma geri yüklendi."
      +
      +  retry:
      +    no_previous:           "Yeniden denenecek önceki mesaj yok."
      +
      +  rollback:
      +    not_enabled:           "Kontrol noktaları etkin değil.\nconfig.yaml içinde etkinleştirin:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "{cwd} için kontrol noktası bulunamadı"
      +    invalid_number:        "Geçersiz kontrol noktası numarası. 1-{max} aralığını kullanın."
      +    restored:              "✅ {hash} kontrol noktasına geri yüklendi: {reason}\nGeri alma öncesi anlık görüntü otomatik olarak kaydedildi."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Ana kanal kaydedilemedi: {error}"
      +    success:               "✅ Ana kanal **{name}** (ID: {chat_id}) olarak ayarlandı.\nCron işleri ve platformlar arası mesajlar buraya iletilecek."
      +
      +  status:
      +    header:                "📊 **Hermes Gateway Durumu**"
      +    session_id:            "**Oturum kimliği:** `{session_id}`"
      +    title:                 "**Başlık:** {title}"
      +    created:               "**Oluşturuldu:** {timestamp}"
      +    last_activity:         "**Son etkinlik:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Aracı çalışıyor:** {state}"
      +    state_yes:             "Evet ⚡"
      +    state_no:              "Hayır"
      +    queued:                "**Sıradaki devam:** {count}"
      +    platforms:             "**Bağlı platformlar:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Durduruldu. Ajan henüz başlamamıştı — bu oturuma devam edebilirsin."
      +    stopped:               "⚡ Durduruldu. Bu oturuma devam edebilirsin."
      +    no_active:             "Durdurulacak aktif görev yok."
      +
      +  title:
      +    db_unavailable:        "Oturum veritabanı kullanılamıyor."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Temizlemeden sonra başlık boş. Lütfen yazdırılabilir karakterler kullanın."
      +    set_to:                "✏️ Oturum başlığı ayarlandı: **{title}**"
      +    not_found:             "Oturum veritabanında bulunamadı."
      +    current_with_title:    "📌 Oturum: `{session_id}`\nBaşlık: **{title}**"
      +    current_no_title:      "📌 Oturum: `{session_id}`\nBaşlık ayarlanmamış. Kullanım: `/title Oturum Adım`"
      +
      +  topic:
      +    not_telegram_dm:         "/topic komutu yalnızca Telegram özel sohbetlerinde kullanılabilir."
      +    no_session_db:           "Oturum veritabanı kullanılamıyor."
      +    unauthorized:            "Bu bot üzerinde /topic kullanma yetkiniz yok."
      +    restore_needs_topic:     "Bir oturumu geri yüklemek için önce bir Telegram topic oluşturun veya açın, ardından o topic içinde /topic <session-id> gönderin. Yeni bir topic oluşturmak için All Messages'ı açıp orada herhangi bir mesaj gönderin."
      +    topics_disabled:         "Bu bot için Telegram topic'leri henüz etkin değil.\n\nNasıl etkinleştirilir:\n1. @BotFather'ı açın.\n2. Botunuzu seçin.\n3. Bot Settings → Threads Settings'ı açın.\n4. Threaded Mode'u açın ve kullanıcıların yeni thread oluşturmasına izin verildiğinden emin olun.\n\nArdından /topic'i tekrar gönderin."
      +    topics_user_disallowed:  "Telegram topic'leri etkin, ancak kullanıcıların topic oluşturmasına izin verilmiyor.\n\n@BotFather → botunuz → Bot Settings → Threads Settings yolunu açın ve 'Disallow users to create new threads' seçeneğini kapatın.\n\nArdından /topic'i tekrar gönderin."
      +    enable_failed:           "Telegram topic modu etkinleştirilemedi: {error}"
      +    bound_status:            "Bu topic şuna bağlı:\nOturum: {label}\nID: {session_id}\n\nBu topic'i yeni bir oturumla değiştirmek için /new kullanın.\nParalel çalışma için All Messages'ı açıp orada bir mesaj göndererek başka bir topic oluşturun."
      +    thread_ready:            "Telegram çok oturumlu topic'leri etkin.\n\nBu topic bağımsız bir Hermes oturumu olarak kullanılacak. Bu topic'in mevcut oturumunu değiştirmek için /new kullanın. Paralel çalışma için All Messages'ı açıp orada bir mesaj göndererek başka bir topic oluşturun."
      +    untitled_session:        "Adsız oturum"
      +
      +  undo:
      +    nothing:               "Geri alınacak bir şey yok."
      +    removed:               "↩️ {count} mesaj geri alındı.\nKaldırıldı: \"{preview}\""
      +
      +  update:
      +    platform_not_messaging:  "✗ /update yalnızca mesajlaşma platformlarında kullanılabilir. Terminalden `hermes update` komutunu çalıştırın."
      +    not_git_repo:            "✗ Git deposu değil — güncellenemiyor."
      +    hermes_cmd_not_found:    "✗ `hermes` komutu bulunamadı. Hermes çalışıyor, ancak güncelleme komutu yürütülebilir dosyayı PATH'te veya mevcut Python yorumlayıcısı aracılığıyla bulamadı. Terminalde `hermes update` komutunu manuel olarak çalıştırmayı deneyin."
      +    start_failed:            "✗ Güncelleme başlatılamadı: {error}"
      +    starting:                "⚕ Hermes güncellemesi başlatılıyor… İlerlemeyi buraya akıtacağım."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Hız Sınırları:** {state}"
      +    header_session:           "📊 **Oturum Token Kullanımı**"
      +    label_model:              "Model: `{model}`"
      +    label_input_tokens:       "Girdi token'ları: {count}"
      +    label_cache_read:         "Önbellek okuma token'ları: {count}"
      +    label_cache_write:        "Önbellek yazma token'ları: {count}"
      +    label_output_tokens:      "Çıktı token'ları: {count}"
      +    label_total:              "Toplam: {count}"
      +    label_api_calls:          "API çağrıları: {count}"
      +    label_cost:               "Maliyet: {prefix}${amount}"
      +    label_cost_included:      "Maliyet: dahil"
      +    label_context:            "Bağlam: {used} / {total} ({pct}%)"
      +    label_compressions:       "Sıkıştırmalar: {count}"
      +    header_session_info:      "📊 **Oturum Bilgisi**"
      +    label_messages:           "Mesajlar: {count}"
      +    label_estimated_context:  "Tahmini bağlam: ~{count} token"
      +    detailed_after_first:     "_(Ayrıntılı kullanım, ilk ajan yanıtından sonra kullanılabilir)_"
      +    no_data:                  "Bu oturum için kullanım verisi yok."
      +
      +  verbose:
      +    not_enabled:           "`/verbose` komutu mesajlaşma platformlarında etkin değil.\n\n`config.yaml` içinde etkinleştirin:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Araç ilerlemesi: **OFF** — araç etkinliği gösterilmez."
      +    mode_new:              "⚙️ Araç ilerlemesi: **NEW** — araç değiştiğinde gösterilir (önizleme uzunluğu: `display.tool_preview_length`, varsayılan 40)."
      +    mode_all:              "⚙️ Araç ilerlemesi: **ALL** — her araç çağrısı gösterilir (önizleme uzunluğu: `display.tool_preview_length`, varsayılan 40)."
      +    mode_verbose:          "⚙️ Araç ilerlemesi: **VERBOSE** — her araç çağrısı tüm argümanlarıyla gösterilir."
      +    saved_suffix:          "_(**{platform}** için kaydedildi — sonraki mesajda geçerli olur)_"
      +    save_failed:           "_(yapılandırmaya kaydedilemedi: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Sesli mod etkinleştirildi.\nSesli mesaj gönderdiğinizde sesli yanıt vereceğim.\nTüm mesajlara sesli yanıt almak için /voice tts kullanın."
      +    disabled_text:         "Sesli mod devre dışı. Yalnızca metin yanıtları."
      +    tts_enabled:           "Otomatik TTS etkinleştirildi.\nTüm yanıtlar bir sesli mesaj içerecek."
      +    status_mode:           "Sesli mod: {label}"
      +    status_channel:        "Ses kanalı: #{channel}"
      +    status_participants:   "Katılımcılar: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (konuşuyor)"
      +    enabled_short:         "Sesli mod etkinleştirildi."
      +    disabled_short:        "Sesli mod devre dışı."
      +    label_off:             "Kapalı (yalnızca metin)"
      +    label_voice_only:      "Açık (sesli mesajlara sesli yanıt)"
      +    label_all:             "TTS (tüm mesajlara sesli yanıt)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Bu oturumda YOLO modu **KAPALI** — tehlikeli komutlar onay gerektirecek."
      +    enabled:                    "⚡ Bu oturumda YOLO modu **AÇIK** — tüm komutlar otomatik onaylanır. Dikkatli kullanın."
      +
      +  shared:
      +    session_db_unavailable:      "Oturum veritabanı kullanılamıyor."
      +    session_db_unavailable_prefix: "Oturum veritabanı kullanılamıyor"
      +    session_not_found:           "Oturum veritabanında bulunamadı."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/uk.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "Немає активної цілі."
         config_read_failed: "⚠️ Не вдалося прочитати config.yaml: {error}"
         config_save_failed: "⚠️ Не вдалося зберегти конфігурацію: {error}"
      +
      +  model:
      +    error_prefix:           "Помилка: {error}"
      +    switched:               "Модель змінено на `{model}`"
      +    provider_label:         "Провайдер: {provider}"
      +    context_label:          "Контекст: {tokens} токенів"
      +    max_output_label:       "Макс. вихід: {tokens} токенів"
      +    cost_label:             "Вартість: {cost}"
      +    capabilities_label:     "Можливості: {capabilities}"
      +    prompt_caching_enabled: "Кешування промптів: увімкнено"
      +    warning_prefix:         "Попередження: {warning}"
      +    saved_global:           "Збережено в config.yaml (`--global`)"
      +    session_only_hint:      "_(лише для цього сеансу — додайте `--global`, щоб зберегти)_"
      +    current_label:          "Поточна: `{model}` на {provider}"
      +    current_tag:            " (поточна)"
      +    more_models_suffix:     " (+{count} ще)"
      +    usage_switch_model:     "`/model <name>` — змінити модель"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — змінити провайдера"
      +    usage_persist:          "`/model <name> --global` — зберегти назавжди"
      +
      +  agents:
      +    header:                "🤖 **Активні агенти та завдання**"
      +    active_agents:         "**Активні агенти:** {count}"
      +    this_chat:             " · цей чат"
      +    more:                  "... і ще {count}"
      +    running_processes:     "**Фонові процеси, що виконуються:** {count}"
      +    async_jobs:            "**Асинхронні задачі гейтвея:** {count}"
      +    none:                  "Немає активних агентів або задач."
      +    state_starting:        "запускається"
      +    state_running:         "виконується"
      +
      +  approve:
      +    no_pending:            "Немає команди на схвалення."
      +    once_singular:         "✅ Команду схвалено. Агент відновлює роботу…"
      +    once_plural:           "✅ Команди схвалено ({count} команд). Агент відновлює роботу…"
      +    session_singular:      "✅ Команду схвалено (шаблон схвалено для цього сеансу). Агент відновлює роботу…"
      +    session_plural:        "✅ Команди схвалено (шаблон схвалено для цього сеансу) ({count} команд). Агент відновлює роботу…"
      +    always_singular:       "✅ Команду схвалено (шаблон схвалено назавжди). Агент відновлює роботу…"
      +    always_plural:         "✅ Команди схвалено (шаблон схвалено назавжди) ({count} команд). Агент відновлює роботу…"
      +
      +  background:
      +    usage:                 "Використання: /background <запит>\nПриклад: /background Підсумуй найкращі історії з HN сьогодні\n\nЗапускає запит в окремому сеансі. Можна продовжити спілкування — результат з'явиться тут після завершення."
      +    started:               "🔄 Фонове завдання запущено: «{preview}»\nID завдання: {task_id}\nМожна продовжити спілкування — результати з'являться тут після завершення."
      +
      +  branch:
      +    db_unavailable:        "База даних сеансів недоступна."
      +    no_conversation:       "Немає розмови для розгалуження — спочатку надішліть повідомлення."
      +    create_failed:         "Не вдалося створити гілку: {error}"
      +    switch_failed:         "Гілку створено, але не вдалося переключитися на неї."
      +    branched_one:          "⑂ Створено гілку **{title}** (скопійовано {count} повідомлення)\nОригінал: `{parent}`\nГілка: `{new}`\nВикористайте `/resume`, щоб повернутися до оригіналу."
      +    branched_many:         "⑂ Створено гілку **{title}** (скопійовано {count} повідомлень)\nОригінал: `{parent}`\nГілка: `{new}`\nВикористайте `/resume`, щоб повернутися до оригіналу."
      +
      +  commands:
      +    usage:                 "Використання: `/commands [page]`"
      +    skill_header:          "⚡ **Команди навичок**:"
      +    default_desc:          "Команда навички"
      +    none:                  "Немає доступних команд."
      +    header:                "📚 **Команди** (всього {total}, сторінка {page}/{total_pages})"
      +    nav_prev:              "`/commands {page}` ← попередня"
      +    nav_next:              "наступна → `/commands {page}`"
      +    out_of_range:          "_(Запитана сторінка {requested} поза межами, показано сторінку {page}.)_"
      +
      +  compress:
      +    not_enough:            "Недостатньо розмови для стиснення (потрібно щонайменше 4 повідомлення)."
      +    no_provider:           "Постачальника не налаштовано — неможливо стиснути."
      +    nothing_to_do:         "Поки що немає що стискати (стенограма все ще є повністю захищеним контекстом)."
      +    focus_line:            "Фокус: \"{topic}\""
      +    summary_failed:        "⚠️ Не вдалося згенерувати зведення ({error}). {count} історичних повідомлень було видалено та замінено заповнювачем; попередній контекст більше не можна відновити. Перевірте конфігурацію моделі auxiliary.compression."
      +    aux_failed:            "ℹ️ Налаштована модель стиснення `{model}` зазнала збою ({error}). Відновлено за допомогою основної моделі — контекст не пошкоджений — але варто перевірити `auxiliary.compression.model` у config.yaml."
      +    failed:                "Стиснення не вдалося: {error}"
      +
      +  debug:
      +    upload_failed:         "✗ Не вдалося завантажити звіт налагодження: {error}"
      +    header:                "**Звіт налагодження завантажено:**"
      +    auto_delete:           "⏱ Вставки автоматично видаляться через 6 годин."
      +    full_logs_hint:        "Щоб завантажити повні журнали, використайте `hermes debug share` з CLI."
      +    share_hint:            "Поділіться цими посиланнями з командою Hermes для отримання підтримки."
      +
      +  deny:
      +    stale:                 "❌ Команду відхилено (схвалення застаріло)."
      +    no_pending:            "Немає команди для відхилення."
      +    denied_singular:       "❌ Команду відхилено."
      +    denied_plural:         "❌ Команди відхилено ({count} команд)."
      +
      +  fast:
      +    not_supported:         "⚡ /fast доступний лише для моделей OpenAI, які підтримують Priority Processing."
      +    status:                "⚡ Priority Processing\n\nПоточний режим: `{mode}`\n\n_Використання:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ Невідомий аргумент: `{arg}`\n\n**Допустимі варіанти:** normal, fast, status"
      +    saved:                 "⚡ ✓ Priority Processing: **{label}** (збережено в конфігурації)\n_(набуде чинності з наступного повідомлення)_"
      +    session_only:          "⚡ ✓ Priority Processing: **{label}** (лише ця сесія)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 Нижній колонтитул середовища: **{state}**\nПоля: `{fields}`\nПлатформа: `{platform}`"
      +    usage:                 "Використання: `/footer [on|off|status]`"
      +    saved:                 "📎 Нижній колонтитул середовища: **{state}**{example}\n_(збережено глобально — набуде чинності з наступного повідомлення)_"
      +    example_line:          "\nПриклад: `{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "Цілі недоступні в цій сесії."
      +    no_goal_set:           "Ціль не встановлено."
      +    paused:                "⏸ Ціль призупинено: {goal}"
      +    no_resume:             "Немає цілі для продовження."
      +    resumed:               "▶ Ціль відновлено: {goal}\nНадішліть будь-яке повідомлення, щоб продовжити, або зачекайте — я зроблю наступний крок у наступному ході."
      +    invalid:               "Неприпустима ціль: {error}"
      +    set:                   "⊙ Ціль встановлено (бюджет {budget} ходів): {goal}\nЯ продовжуватиму працювати, доки ціль не буде досягнута, ви її не призупините/очистите, або бюджет не вичерпається.\nКерування: /goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Команди Hermes**\n"
      +    skill_header:          "\n⚡ **Команди навичок** ({count} активних):"
      +    more_use_commands:     "\n... і ще {count}. Використайте `/commands` для повного списку зі сторінками."
      +
      +  insights:
      +    invalid_days:          "Недійсне значення --days: {value}"
      +    error:                 "Помилка при формуванні аналітики: {error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ помилка kanban: {error}"
      +    subscribed_suffix:     "(підписано — ви отримаєте сповіщення, коли {task_id} завершиться або буде заблоковано)"
      +    truncated_suffix:      "… (скорочено; використовуйте `hermes kanban …` у терміналі для повного виводу)"
      +    no_output:             "(немає виводу)"
      +
      +  personality:
      +    none_configured:       "У `{path}/config.yaml` не налаштовано жодної особистості"
      +    header:                "🎭 **Доступні особистості**\n"
      +    none_option:           "• `none` — (без накладання особистості)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\nВикористання: `/personality <name>`"
      +    save_failed:           "⚠️ Не вдалося зберегти зміну особистості: {error}"
      +    cleared:               "🎭 Особистість очищено — використовується базова поведінка агента.\n_(набуде чинності з наступного повідомлення)_"
      +    set_to:                "🎭 Особистість встановлено на **{name}**\n_(набуде чинності з наступного повідомлення)_"
      +    unknown:               "Невідома особистість: `{name}`\n\nДоступні: {available}"
      +
      +  profile:
      +    header:                "👤 **Профіль:** `{profile}`"
      +    home:                  "📂 **Домашня тека:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium (за замовчуванням)"
      +    level_disabled:            "none (вимкнено)"
      +    scope_session:             "перевизначення сеансу"
      +    scope_global:              "глобальна конфігурація"
      +    status:                    "🧠 **Налаштування мислення**\n\n**Зусилля:** `{level}`\n**Область:** {scope}\n**Показ:** {display}\n\n_Використання:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "увімкнено ✓"
      +    display_off:               "вимкнено"
      +    display_set_on:            "🧠 ✓ Показ мислення: **УВІМКНЕНО**\nДумки моделі будуть показуватися перед кожною відповіддю на **{platform}**."
      +    display_set_off:           "🧠 ✓ Показ мислення: **ВИМКНЕНО** для **{platform}**"
      +    reset_global_unsupported:  "⚠️ `/reasoning reset --global` не підтримується. Використовуйте `/reasoning <level> --global`, щоб змінити глобальне значення за замовчуванням."
      +    reset_done:                "🧠 ✓ Перевизначення мислення для сеансу скинуто; повернення до глобальної конфігурації."
      +    unknown_arg:               "⚠️ Невідомий аргумент: `{arg}`\n\n**Дійсні рівні:** none, minimal, low, medium, high, xhigh\n**Показ:** show, hide\n**Зберегти:** додайте `--global`, щоб зберегти поза цим сеансом"
      +    set_global:                "🧠 ✓ Зусилля мислення встановлено на `{effort}` (збережено в конфігурації)\n_(набуде чинності з наступного повідомлення)_"
      +    set_global_save_failed:    "🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — не вдалося зберегти конфігурацію)\n_(набуде чинності з наступного повідомлення)_"
      +    set_session:               "🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — додайте `--global`, щоб зберегти)\n_(набуде чинності з наступного повідомлення)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 /reload-mcp скасовано. MCP-інструменти без змін."
      +    always_followup:       "ℹ️ Наступні виклики `/reload-mcp` виконуватимуться без підтвердження. Увімкнути знову можна через `approvals.mcp_reload_confirm: true` у `config.yaml`."
      +    confirm_prompt:        "⚠️ **Підтвердження /reload-mcp**\n\nПерезавантаження MCP-серверів перебудовує набір інструментів для цього сеансу та **інвалідує кеш промпта провайдера** — наступне повідомлення повторно надішле всі вхідні токени. На моделях із довгим контекстом або високим рівнем міркувань це може бути дорого.\n\nОберіть:\n• **Схвалити один раз** — перезавантажити зараз\n• **Завжди схвалювати** — перезавантажити та назавжди приховати цей запит\n• **Скасувати** — залишити MCP-інструменти без змін\n\n_Текстова альтернатива: відповідайте `/approve`, `/always` або `/cancel`._"
      +    header:                "🔄 **MCP-сервери перезавантажено**\n"
      +    reconnected:           "♻️ Перепідключено: {names}"
      +    added:                 "➕ Додано: {names}"
      +    removed:               "➖ Видалено: {names}"
      +    none_connected:        "Немає підключених MCP-серверів."
      +    tools_available:       "\n🔧 {tools} інструмент(ів) доступно з {servers} сервер(ів)"
      +    failed:                "❌ Помилка перезавантаження MCP: {error}"
      +
      +  reload_skills:
      +    header:                "🔄 **Навички перезавантажено**\n"
      +    no_new:                "Нових навичок не виявлено."
      +    total:                 "\n📚 {count} навичок(и) доступно"
      +    added_header:          "➕ **Додані навички:**"
      +    removed_header:        "➖ **Видалені навички:**"
      +    item_with_desc:        "    - {name}: {desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ Помилка перезавантаження навичок: {error}"
      +
      +  reset:
      +    header_default:        "✨ Сесію скинуто! Починаємо з чистого аркуша."
      +    header_new:            "✨ Нову сесію запущено!"
      +    header_titled:         "✨ Нову сесію запущено: {title}"
      +    title_rejected:        "\n⚠️ Назву відхилено: {error}"
      +    title_error_untitled:  "\n⚠️ {error} — сесію запущено без назви."
      +    title_empty_untitled:  "\n⚠️ Після очищення назва порожня — сесію запущено без назви."
      +    tip:                   "\n✦ Порада: {tip}"
      +
      +  restart:
      +    in_progress:           "⏳ Перезапуск гейтвея вже виконується..."
      +    restarting:            "♻ Перезапуск гейтвея. Якщо ви не отримаєте сповіщення протягом 60 секунд, перезапустіть із консолі командою `hermes gateway restart`."
      +
      +  resume:
      +    db_unavailable:        "База даних сеансів недоступна."
      +    no_named_sessions:     "Іменованих сеансів не знайдено.\nВикористайте `/title Мій сеанс`, щоб назвати поточний сеанс, потім `/resume Мій сеанс`, щоб повернутися до нього."
      +    list_header:           "📋 **Іменовані сеанси**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\nВикористання: `/resume <назва сеансу>`"
      +    list_failed:           "Не вдалося отримати список сеансів: {error}"
      +    not_found:             "Сеанс, що відповідає '**{name}**', не знайдено.\nВикористайте `/resume` без аргументів, щоб побачити доступні сеанси."
      +    already_on:            "📌 Уже в сеансі **{name}**."
      +    switch_failed:         "Не вдалося переключити сеанс."
      +    resumed_one:           "↻ Сеанс **{title}** відновлено ({count} повідомлення). Розмову відновлено."
      +    resumed_many:          "↻ Сеанс **{title}** відновлено ({count} повідомлень). Розмову відновлено."
      +    resumed_no_count:      "↻ Сеанс **{title}** відновлено. Розмову відновлено."
      +
      +  retry:
      +    no_previous:           "Немає попереднього повідомлення для повторення."
      +
      +  rollback:
      +    not_enabled:           "Контрольні точки не ввімкнено.\nУвімкніть у config.yaml:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "Контрольних точок для {cwd} не знайдено"
      +    invalid_number:        "Недійсний номер контрольної точки. Використовуйте 1-{max}."
      +    restored:              "✅ Відновлено до контрольної точки {hash}: {reason}\nЗнімок перед відкатом збережено автоматично."
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "Не вдалося зберегти головний канал: {error}"
      +    success:               "✅ Головний канал встановлено на **{name}** (ID: {chat_id}).\nCron-завдання та міжплатформні повідомлення доставлятимуться сюди."
      +
      +  status:
      +    header:                "📊 **Стан Hermes Gateway**"
      +    session_id:            "**ID сесії:** `{session_id}`"
      +    title:                 "**Назва:** {title}"
      +    created:               "**Створено:** {timestamp}"
      +    last_activity:         "**Остання активність:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] {tokens}"
      +    agent_running:         "**Агент активний:** {state}"
      +    state_yes:             "Так ⚡"
      +    state_no:              "Ні"
      +    queued:                "**Черга продовжень:** {count}"
      +    platforms:             "**Підключені платформи:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ Зупинено. Агент ще не починав — можна продовжити цей сеанс."
      +    stopped:               "⚡ Зупинено. Можна продовжити цей сеанс."
      +    no_active:             "Немає активного завдання для зупинки."
      +
      +  title:
      +    db_unavailable:        "База даних сеансів недоступна."
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ Після очищення назва порожня. Використовуйте друковані символи."
      +    set_to:                "✏️ Назву сеансу встановлено: **{title}**"
      +    not_found:             "Сеанс не знайдено в базі даних."
      +    current_with_title:    "📌 Сеанс: `{session_id}`\nНазва: **{title}**"
      +    current_no_title:      "📌 Сеанс: `{session_id}`\nНазву не встановлено. Використання: `/title Назва мого сеансу`"
      +
      +  topic:
      +    not_telegram_dm:         "Команда /topic доступна лише в приватних чатах Telegram."
      +    no_session_db:           "База даних сесій недоступна."
      +    unauthorized:            "Ви не маєте дозволу використовувати /topic у цьому боті."
      +    restore_needs_topic:     "Щоб відновити сесію, спочатку створіть або відкрийте Telegram topic, а потім надішліть /topic <session-id> у цьому topic. Щоб створити новий topic, відкрийте All Messages і надішліть там будь-яке повідомлення."
      +    topics_disabled:         "Telegram topics ще не ввімкнено для цього бота.\n\nЯк увімкнути:\n1. Відкрийте @BotFather.\n2. Виберіть свого бота.\n3. Відкрийте Bot Settings → Threads Settings.\n4. Увімкніть Threaded Mode і переконайтеся, що користувачам дозволено створювати нові threads.\n\nПотім надішліть /topic знову."
      +    topics_user_disallowed:  "Telegram topics увімкнено, але користувачам не дозволено створювати topics.\n\nВідкрийте @BotFather → виберіть свого бота → Bot Settings → Threads Settings, потім вимкніть 'Disallow users to create new threads'.\n\nПотім надішліть /topic знову."
      +    enable_failed:           "Не вдалося ввімкнути режим Telegram topic: {error}"
      +    bound_status:            "Цей topic пов'язано з:\nСесія: {label}\nID: {session_id}\n\nВикористовуйте /new, щоб замінити цей topic новою сесією.\nДля паралельної роботи відкрийте All Messages і надішліть там повідомлення, щоб створити інший topic."
      +    thread_ready:            "Багатосесійні Telegram topics увімкнено.\n\nЦей topic використовуватиметься як незалежна сесія Hermes. Використовуйте /new, щоб замінити поточну сесію цього topic. Для паралельної роботи відкрийте All Messages і надішліть там повідомлення, щоб створити інший topic."
      +    untitled_session:        "Сесія без назви"
      +
      +  undo:
      +    nothing:               "Немає чого скасовувати."
      +    removed:               "↩️ Скасовано {count} повідомлень.\nВидалено: «{preview}»"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update доступний лише на платформах обміну повідомленнями. Виконайте `hermes update` у терміналі."
      +    not_git_repo:            "✗ Не git-репозиторій — оновлення неможливе."
      +    hermes_cmd_not_found:    "✗ Не вдалося знайти команду `hermes`. Hermes запущено, але команда оновлення не знайшла виконуваний файл у PATH або через поточний інтерпретатор Python. Спробуйте виконати `hermes update` вручну у вашому терміналі."
      +    start_failed:            "✗ Не вдалося запустити оновлення: {error}"
      +    starting:                "⚕ Запуск оновлення Hermes… Я транслюватиму прогрес сюди."
      +
      +  usage:
      +    rate_limits:              "⏱️ **Обмеження швидкості:** {state}"
      +    header_session:           "📊 **Використання токенів сеансу**"
      +    label_model:              "Модель: `{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment] токени: {count}"
      +    label_cache_read:         "Токени читання кешу: {count}"
      +    label_cache_write:        "Токени запису кешу: {count}"
      +    label_output_tokens:      "[redacted:secret-assignment] токени: {count}"
      +    label_total:              "Усього: {count}"
      +    label_api_calls:          "Виклики API: {count}"
      +    label_cost:               "Вартість: {prefix}${amount}"
      +    label_cost_included:      "Вартість: включено"
      +    label_context:            "Контекст: {used} / {total} ({pct}%)"
      +    label_compressions:       "Стиснень: {count}"
      +    header_session_info:      "📊 **Інформація про сеанс**"
      +    label_messages:           "Повідомлень: {count}"
      +    label_estimated_context:  "Орієнтовний контекст: ~{count} токенів"
      +    detailed_after_first:     "_(Детальне використання доступне після першої відповіді агента)_"
      +    no_data:                  "Дані про використання для цього сеансу відсутні."
      +
      +  verbose:
      +    not_enabled:           "Команду `/verbose` не ввімкнено для платформ обміну повідомленнями.\n\nУвімкніть у `config.yaml`:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ Прогрес інструментів: **OFF** — активність інструментів не показується."
      +    mode_new:              "⚙️ Прогрес інструментів: **NEW** — показується при зміні інструмента (довжина попереднього перегляду: `display.tool_preview_length`, за замовчуванням 40)."
      +    mode_all:              "⚙️ Прогрес інструментів: **ALL** — показується кожен виклик інструмента (довжина попереднього перегляду: `display.tool_preview_length`, за замовчуванням 40)."
      +    mode_verbose:          "⚙️ Прогрес інструментів: **VERBOSE** — кожен виклик інструмента з повними аргументами."
      +    saved_suffix:          "_(збережено для **{platform}** — набуде чинності з наступного повідомлення)_"
      +    save_failed:           "_(не вдалося зберегти у конфігурацію: {error})_"
      +
      +  voice:
      +    enabled_voice_only:    "Голосовий режим увімкнено.\nЯ відповідатиму голосом, коли ви надсилатимете голосові повідомлення.\nВикористайте /voice tts, щоб отримувати голосові відповіді на всі повідомлення."
      +    disabled_text:         "Голосовий режим вимкнено. Лише текстові відповіді."
      +    tts_enabled:           "Авто-TTS увімкнено.\nУсі відповіді міститимуть голосове повідомлення."
      +    status_mode:           "Голосовий режим: {label}"
      +    status_channel:        "Голосовий канал: #{channel}"
      +    status_participants:   "Учасники: {count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              " (говорить)"
      +    enabled_short:         "Голосовий режим увімкнено."
      +    disabled_short:        "Голосовий режим вимкнено."
      +    label_off:             "Вимкнено (лише текст)"
      +    label_voice_only:      "Увімкнено (голосова відповідь на голосові повідомлення)"
      +    label_all:             "TTS (голосова відповідь на всі повідомлення)"
      +
      +  yolo:
      +    disabled:                   "⚠️ Режим YOLO для цього сеансу **ВИМКНЕНО** — небезпечні команди потребуватимуть схвалення."
      +    enabled:                    "⚡ Режим YOLO для цього сеансу **УВІМКНЕНО** — усі команди схвалюються автоматично. Використовуйте з обережністю."
      +
      +  shared:
      +    session_db_unavailable:      "База даних сеансів недоступна."
      +    session_db_unavailable_prefix: "База даних сеансів недоступна"
      +    session_not_found:           "Сеанс не знайдено в базі даних."
      +    warn_passthrough:            "⚠️ {error}"
    • locales/zh-hant.yaml
      @@ -0,0 +1,350 @@
      +# Hermes 靜態訊息目錄 -- 繁體中文(台灣/香港)
      +# See locales/en.yaml for the source of truth; keep keys in sync.
      +
      +approval:
      +  dangerous_header: "⚠️  危險指令: {description}"
      +  choose_long:     "      [o]僅此一次  |  [s]本次工作階段  |  [a]永久允許  |  [d]拒絕"
      +  choose_short:    "      [o]僅此一次  |  [s]本次工作階段  |  [d]拒絕"
      +  prompt_long:     "      選擇 [o/s/a/D]: "
      +  prompt_short:    "      選擇 [o/s/D]: "
      +  timeout:         "      ⏱ 逾時 — 已拒絕指令"
      +  allowed_once:    "      ✓ 本次允許"
      +  allowed_session: "      ✓ 本次工作階段內允許"
      +  allowed_always:  "      ✓ 已加入永久允許清單"
      +  denied:          "      ✗ 已拒絕"
      +  cancelled:       "      ✗ 已取消"
      +  blocklist_message: "此指令位於無條件封鎖清單中,無法被批准。"
      +
      +gateway:
      +  approval_expired: "⚠️ 批准已逾期(代理不再等待)。請讓代理重試。"
      +  draining:         "⏳ 正在等待 {count} 個活躍代理結束後重新啟動..."
      +  goal_cleared:     "✓ 目標已清除。"
      +  no_active_goal:   "目前沒有作用中的目標。"
      +  config_read_failed: "⚠️ 無法讀取 config.yaml:{error}"
      +  config_save_failed: "⚠️ 無法儲存設定:{error}"
      +
      +  model:
      +    error_prefix:           "錯誤:{error}"
      +    switched:               "已切換模型為 `{model}`"
      +    provider_label:         "提供方:{provider}"
      +    context_label:          "上下文:{tokens} tokens"
      +    max_output_label:       "最大輸出:{tokens} tokens"
      +    cost_label:             "費用:{cost}"
      +    capabilities_label:     "能力:{capabilities}"
      +    prompt_caching_enabled: "提示快取:已啟用"
      +    warning_prefix:         "警告:{warning}"
      +    saved_global:           "已儲存到 config.yaml(`--global`)"
      +    session_only_hint:      "_(僅本次工作階段有效 — 加上 `--global` 可永久儲存)_"
      +    current_label:          "目前:`{model}`({provider})"
      +    current_tag:            "(目前)"
      +    more_models_suffix:     "(還有 {count} 個)"
      +    usage_switch_model:     "`/model <name>` — 切換模型"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — 切換提供方"
      +    usage_persist:          "`/model <name> --global` — 永久儲存"
      +
      +  agents:
      +    header:                "🤖 **作用中的代理與任務**"
      +    active_agents:         "**作用中代理:** {count}"
      +    this_chat:             " · 目前聊天"
      +    more:                  "... 還有 {count} 個"
      +    running_processes:     "**執行中的背景程序:** {count}"
      +    async_jobs:            "**閘道非同步任務:** {count}"
      +    none:                  "沒有作用中的代理或執行中的任務。"
      +    state_starting:        "啟動中"
      +    state_running:         "執行中"
      +
      +  approve:
      +    no_pending:            "沒有待批准的指令。"
      +    once_singular:         "✅ 指令已批准。代理正在恢復…"
      +    once_plural:           "✅ 指令已批准({count} 條指令)。代理正在恢復…"
      +    session_singular:      "✅ 指令已批准(本次工作階段內允許該模式)。代理正在恢復…"
      +    session_plural:        "✅ 指令已批准(本次工作階段內允許該模式)({count} 條指令)。代理正在恢復…"
      +    always_singular:       "✅ 指令已批准(永久允許該模式)。代理正在恢復…"
      +    always_plural:         "✅ 指令已批准(永久允許該模式)({count} 條指令)。代理正在恢復…"
      +
      +  background:
      +    usage:                 "用法:/background <提示>\n範例:/background 摘要今天 HN 上的熱門故事\n\n在獨立工作階段中執行該提示。你可以繼續聊天 — 完成後結果將顯示於此。"
      +    started:               "🔄 背景任務已啟動:「{preview}」\n任務 ID:{task_id}\n你可以繼續聊天 — 完成後結果將顯示於此。"
      +
      +  branch:
      +    db_unavailable:        "工作階段資料庫無法使用。"
      +    no_conversation:       "沒有可分支的對話 — 請先傳送一則訊息。"
      +    create_failed:         "建立分支失敗:{error}"
      +    switch_failed:         "分支已建立,但無法切換到該分支。"
      +    branched_one:          "⑂ 已分支至 **{title}**(已複製 {count} 則訊息)\n原始:`{parent}`\n分支:`{new}`\n使用 `/resume` 切換回原始工作階段。"
      +    branched_many:         "⑂ 已分支至 **{title}**(已複製 {count} 則訊息)\n原始:`{parent}`\n分支:`{new}`\n使用 `/resume` 切換回原始工作階段。"
      +
      +  commands:
      +    usage:                 "用法:`/commands [page]`"
      +    skill_header:          "⚡ **技能指令**:"
      +    default_desc:          "技能指令"
      +    none:                  "沒有可用的指令。"
      +    header:                "📚 **指令**(共 {total} 個,第 {page}/{total_pages} 頁)"
      +    nav_prev:              "`/commands {page}` ← 上一頁"
      +    nav_next:              "下一頁 → `/commands {page}`"
      +    out_of_range:          "_(請求的第 {requested} 頁超出範圍,顯示第 {page} 頁。)_"
      +
      +  compress:
      +    not_enough:            "對話內容不足,無法壓縮(至少需要 4 則訊息)。"
      +    no_provider:           "未設定提供方 — 無法壓縮。"
      +    nothing_to_do:         "目前沒有可壓縮的內容(對話記錄仍全部為受保護的上下文)。"
      +    focus_line:            "聚焦:\"{topic}\""
      +    summary_failed:        "⚠️ 摘要產生失敗({error})。{count} 則歷史訊息已被移除並以佔位符取代;先前的上下文已無法復原。建議檢查 auxiliary.compression 模型設定。"
      +    aux_failed:            "ℹ️ 設定的壓縮模型 `{model}` 失敗({error})。已使用主要模型復原 — 上下文完整 — 但您可能想檢查 config.yaml 中的 `auxiliary.compression.model`。"
      +    failed:                "壓縮失敗:{error}"
      +
      +  debug:
      +    upload_failed:         "✗ 無法上傳除錯報告:{error}"
      +    header:                "**除錯報告已上傳:**"
      +    auto_delete:           "⏱ 貼上的內容將於 6 小時後自動刪除。"
      +    full_logs_hint:        "如需上傳完整紀錄,請在 CLI 中使用 `hermes debug share`。"
      +    share_hint:            "請將這些連結分享給 Hermes 團隊以取得支援。"
      +
      +  deny:
      +    stale:                 "❌ 指令已拒絕(批准已過期)。"
      +    no_pending:            "沒有待拒絕的指令。"
      +    denied_singular:       "❌ 指令已拒絕。"
      +    denied_plural:         "❌ 指令已拒絕({count} 條指令)。"
      +
      +  fast:
      +    not_supported:         "⚡ /fast 僅適用於支援 Priority Processing 的 OpenAI 模型。"
      +    status:                "⚡ Priority Processing\n\n目前模式:`{mode}`\n\n_用法:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ 未知參數:`{arg}`\n\n**有效選項:** normal、fast、status"
      +    saved:                 "⚡ ✓ Priority Processing:**{label}**(已儲存到設定)\n_(下一則訊息生效)_"
      +    session_only:          "⚡ ✓ Priority Processing:**{label}**(僅本次工作階段)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 執行階段頁尾:**{state}**\n欄位:`{fields}`\n平台:`{platform}`"
      +    usage:                 "用法:`/footer [on|off|status]`"
      +    saved:                 "📎 執行階段頁尾:**{state}**{example}\n_(已全域儲存 — 下一則訊息生效)_"
      +    example_line:          "\n範例:`{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "此工作階段不支援目標功能。"
      +    no_goal_set:           "未設定目標。"
      +    paused:                "⏸ 目標已暫停:{goal}"
      +    no_resume:             "沒有可恢復的目標。"
      +    resumed:               "▶ 目標已恢復:{goal}\n傳送任意訊息繼續,或等待 — 我會在下一輪繼續推進。"
      +    invalid:               "無效目標:{error}"
      +    set:                   "⊙ 目標已設定({budget} 輪預算):{goal}\n我會持續工作直到目標完成、你暫停/清除目標,或預算耗盡。\n控制指令:/goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes 指令**\n"
      +    skill_header:          "\n⚡ **技能指令**({count} 個作用中):"
      +    more_use_commands:     "\n... 還有 {count} 個。使用 `/commands` 檢視完整分頁清單。"
      +
      +  insights:
      +    invalid_days:          "無效的 --days 值:{value}"
      +    error:                 "產生洞察時發生錯誤:{error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban 錯誤:{error}"
      +    subscribed_suffix:     "(已訂閱 — 當 {task_id} 完成或被封鎖時將通知您)"
      +    truncated_suffix:      "…(已截斷;如需完整輸出請在終端機執行 `hermes kanban …`)"
      +    no_output:             "(無輸出)"
      +
      +  personality:
      +    none_configured:       "`{path}/config.yaml` 中未設定人格"
      +    header:                "🎭 **可用人格**\n"
      +    none_option:           "• `none` —(不套用人格覆寫)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\n用法:`/personality <name>`"
      +    save_failed:           "⚠️ 儲存人格變更失敗:{error}"
      +    cleared:               "🎭 已清除人格 — 使用基礎代理行為。\n_(下一則訊息生效)_"
      +    set_to:                "🎭 人格已設定為 **{name}**\n_(下一則訊息生效)_"
      +    unknown:               "未知人格:`{name}`\n\n可用:{available}"
      +
      +  profile:
      +    header:                "👤 **設定檔:** `{profile}`"
      +    home:                  "📂 **主目錄:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium(預設)"
      +    level_disabled:            "none(已停用)"
      +    scope_session:             "工作階段覆寫"
      +    scope_global:              "全域設定"
      +    status:                    "🧠 **推理設定**\n\n**強度:** `{level}`\n**範圍:** {scope}\n**顯示:** {display}\n\n_用法:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "開啟 ✓"
      +    display_off:               "關閉"
      +    display_set_on:            "🧠 ✓ 推理顯示:**開啟**\n在 **{platform}** 上每次回應前將顯示模型的思考過程。"
      +    display_set_off:           "🧠 ✓ **{platform}** 上的推理顯示:**關閉**"
      +    reset_global_unsupported:  "⚠️ 不支援 `/reasoning reset --global`。請使用 `/reasoning <level> --global` 變更全域預設值。"
      +    reset_done:                "🧠 ✓ 已清除本工作階段的推理覆寫;回退至全域設定。"
      +    unknown_arg:               "⚠️ 未知參數:`{arg}`\n\n**有效級別:** none, minimal, low, medium, high, xhigh\n**顯示:** show, hide\n**持久化:** 加上 `--global` 可跨工作階段儲存"
      +    set_global:                "🧠 ✓ 推理強度已設定為 `{effort}`(已儲存到設定)\n_(下一則訊息生效)_"
      +    set_global_save_failed:    "🧠 ✓ 推理強度已設定為 `{effort}`(僅本工作階段 — 設定儲存失敗)\n_(下一則訊息生效)_"
      +    set_session:               "🧠 ✓ 推理強度已設定為 `{effort}`(僅本工作階段 — 加上 `--global` 可持久化)\n_(下一則訊息生效)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 已取消 /reload-mcp。MCP 工具未變更。"
      +    always_followup:       "ℹ️ 後續 `/reload-mcp` 呼叫將不再要求確認。可在 `config.yaml` 中將 `approvals.mcp_reload_confirm: true` 重新啟用。"
      +    confirm_prompt:        "⚠️ **確認 /reload-mcp**\n\n重新載入 MCP 伺服器會為本工作階段重建工具集,並**使提供方提示快取失效** — 下一則訊息將重新傳送完整輸入 token。在長上下文或高推理模型上,這可能成本較高。\n\n請選擇:\n• **批准一次** — 立即重新載入\n• **永遠批准** — 立即重新載入並永久關閉此提示\n• **取消** — 保持 MCP 工具不變\n\n_文字備援:回覆 `/approve`、`/always` 或 `/cancel`。_"
      +    header:                "🔄 **MCP 伺服器已重新載入**\n"
      +    reconnected:           "♻️ 已重新連線:{names}"
      +    added:                 "➕ 已新增:{names}"
      +    removed:               "➖ 已移除:{names}"
      +    none_connected:        "沒有已連線的 MCP 伺服器。"
      +    tools_available:       "\n🔧 來自 {servers} 個伺服器的 {tools} 個工具可用"
      +    failed:                "❌ MCP 重新載入失敗:{error}"
      +
      +  reload_skills:
      +    header:                "🔄 **技能已重新載入**\n"
      +    no_new:                "未偵測到新技能。"
      +    total:                 "\n📚 {count} 個技能可用"
      +    added_header:          "➕ **新增技能:**"
      +    removed_header:        "➖ **移除技能:**"
      +    item_with_desc:        "    - {name}:{desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ 技能重新載入失敗:{error}"
      +
      +  reset:
      +    header_default:        "✨ 工作階段已重設!重新開始。"
      +    header_new:            "✨ 新工作階段已啟動!"
      +    header_titled:         "✨ 新工作階段已啟動:{title}"
      +    title_rejected:        "\n⚠️ 標題遭拒絕:{error}"
      +    title_error_untitled:  "\n⚠️ {error} — 工作階段以未命名方式啟動。"
      +    title_empty_untitled:  "\n⚠️ 清理後標題為空 — 工作階段以未命名方式啟動。"
      +    tip:                   "\n✦ 提示:{tip}"
      +
      +  restart:
      +    in_progress:           "⏳ 閘道重新啟動已在進行中……"
      +    restarting:            "♻ 正在重新啟動閘道。如果 60 秒內未收到通知,請在主控台執行 `hermes gateway restart` 重新啟動。"
      +
      +  resume:
      +    db_unavailable:        "工作階段資料庫無法使用。"
      +    no_named_sessions:     "找不到已命名的工作階段。\n使用 `/title 我的工作階段` 為目前工作階段命名,然後使用 `/resume 我的工作階段` 返回。"
      +    list_header:           "📋 **已命名工作階段**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\n用法:`/resume <工作階段名稱>`"
      +    list_failed:           "無法列出工作階段:{error}"
      +    not_found:             "找不到符合 '**{name}**' 的工作階段。\n使用不帶參數的 `/resume` 檢視可用的工作階段。"
      +    already_on:            "📌 已在工作階段 **{name}** 上。"
      +    switch_failed:         "切換工作階段失敗。"
      +    resumed_one:           "↻ 已恢復工作階段 **{title}**({count} 則訊息)。對話已還原。"
      +    resumed_many:          "↻ 已恢復工作階段 **{title}**({count} 則訊息)。對話已還原。"
      +    resumed_no_count:      "↻ 已恢復工作階段 **{title}**。對話已還原。"
      +
      +  retry:
      +    no_previous:           "沒有可重試的上一則訊息。"
      +
      +  rollback:
      +    not_enabled:           "檢查點未啟用。\n請在 config.yaml 中啟用:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "找不到 {cwd} 的檢查點"
      +    invalid_number:        "無效的檢查點編號。請使用 1-{max}。"
      +    restored:              "✅ 已還原至檢查點 {hash}:{reason}\n已自動儲存回復前的快照。"
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "無法儲存主頻道:{error}"
      +    success:               "✅ 主頻道已設定為 **{name}**(ID:{chat_id})。\n排程任務和跨平台訊息將傳送至此處。"
      +
      +  status:
      +    header:                "📊 **Hermes 閘道狀態**"
      +    session_id:            "**工作階段 ID:** `{session_id}`"
      +    title:                 "**標題:** {title}"
      +    created:               "**建立時間:** {timestamp}"
      +    last_activity:         "**最近活動:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] 數:** {tokens}"
      +    agent_running:         "**代理執行中:** {state}"
      +    state_yes:             "是 ⚡"
      +    state_no:              "否"
      +    queued:                "**排隊中的後續:** {count}"
      +    platforms:             "**已連線平台:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ 已停止。代理尚未啟動 — 你可以繼續此工作階段。"
      +    stopped:               "⚡ 已停止。你可以繼續此工作階段。"
      +    no_active:             "沒有可停止的作用中任務。"
      +
      +  title:
      +    db_unavailable:        "工作階段資料庫無法使用。"
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ 清理後標題為空。請使用可列印字元。"
      +    set_to:                "✏️ 已設定工作階段標題:**{title}**"
      +    not_found:             "在資料庫中找不到此工作階段。"
      +    current_with_title:    "📌 工作階段:`{session_id}`\n標題:**{title}**"
      +    current_no_title:      "📌 工作階段:`{session_id}`\n尚未設定標題。用法:`/title 我的工作階段名稱`"
      +
      +  topic:
      +    not_telegram_dm:         "/topic 指令僅在 Telegram 私人聊天中可用。"
      +    no_session_db:           "工作階段資料庫無法使用。"
      +    unauthorized:            "您無權在此 bot 上使用 /topic。"
      +    restore_needs_topic:     "若要恢復工作階段,請先建立或開啟一個 Telegram topic,然後在該 topic 中傳送 /topic <session-id>。若要建立新 topic,請開啟 All Messages 並在其中傳送任意訊息。"
      +    topics_disabled:         "此 bot 尚未啟用 Telegram topics。\n\n啟用方法:\n1. 開啟 @BotFather。\n2. 選擇您的 bot。\n3. 開啟 Bot Settings → Threads Settings。\n4. 開啟 Threaded Mode,並確保允許使用者建立新 thread。\n\n然後再次傳送 /topic。"
      +    topics_user_disallowed:  "Telegram topics 已啟用,但不允許使用者建立 topics。\n\n開啟 @BotFather → 選擇您的 bot → Bot Settings → Threads Settings,然後關閉 'Disallow users to create new threads'。\n\n然後再次傳送 /topic。"
      +    enable_failed:           "啟用 Telegram topic 模式失敗:{error}"
      +    bound_status:            "此 topic 已連結至:\n工作階段:{label}\nID:{session_id}\n\n使用 /new 將此 topic 取代為新工作階段。\n如需平行作業,請開啟 All Messages 並在其中傳送訊息以建立另一個 topic。"
      +    thread_ready:            "Telegram 多工作階段 topics 已啟用。\n\n此 topic 將作為獨立的 Hermes 工作階段使用。使用 /new 取代此 topic 目前的工作階段。如需平行作業,請開啟 All Messages 並在其中傳送訊息以建立另一個 topic。"
      +    untitled_session:        "未命名工作階段"
      +
      +  undo:
      +    nothing:               "沒有可復原的內容。"
      +    removed:               "↩️ 已復原 {count} 則訊息。\n已移除:「{preview}」"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update 僅在訊息平台上可用。請在終端機執行 `hermes update`。"
      +    not_git_repo:            "✗ 不是 git 儲存庫 — 無法更新。"
      +    hermes_cmd_not_found:    "✗ 找不到 `hermes` 指令。Hermes 正在執行,但更新指令無法在 PATH 上或透過目前的 Python 解譯器找到執行檔。請嘗試在終端機中手動執行 `hermes update`。"
      +    start_failed:            "✗ 啟動更新失敗:{error}"
      +    starting:                "⚕ 正在啟動 Hermes 更新…… 進度將在此處顯示。"
      +
      +  usage:
      +    rate_limits:              "⏱️ **速率限制:** {state}"
      +    header_session:           "📊 **工作階段 token 使用情況**"
      +    label_model:              "模型:`{model}`"
      +    label_input_tokens:       "輸入 token:{count}"
      +    label_cache_read:         "快取讀取 token:{count}"
      +    label_cache_write:        "快取寫入 token:{count}"
      +    label_output_tokens:      "輸出 token:{count}"
      +    label_total:              "總計:{count}"
      +    label_api_calls:          "API 呼叫次數:{count}"
      +    label_cost:               "費用:{prefix}${amount}"
      +    label_cost_included:      "費用:已包含"
      +    label_context:            "上下文:{used} / {total}({pct}%)"
      +    label_compressions:       "壓縮次數:{count}"
      +    header_session_info:      "📊 **工作階段資訊**"
      +    label_messages:           "訊息數:{count}"
      +    label_estimated_context:  "預估上下文:~{count} 個 token"
      +    detailed_after_first:     "_(首次代理回應後可檢視詳細使用情況)_"
      +    no_data:                  "此工作階段沒有可用的使用資料。"
      +
      +  verbose:
      +    not_enabled:           "`/verbose` 指令未在訊息平台上啟用。\n\n請在 `config.yaml` 中啟用:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ 工具進度:**OFF** — 不顯示任何工具活動。"
      +    mode_new:              "⚙️ 工具進度:**NEW** — 工具變更時顯示(預覽長度:`display.tool_preview_length`,預設 40)。"
      +    mode_all:              "⚙️ 工具進度:**ALL** — 顯示每次工具呼叫(預覽長度:`display.tool_preview_length`,預設 40)。"
      +    mode_verbose:          "⚙️ 工具進度:**VERBOSE** — 顯示每次工具呼叫及完整參數。"
      +    saved_suffix:          "_(已為 **{platform}** 儲存 — 下一則訊息生效)_"
      +    save_failed:           "_(無法儲存到設定:{error})_"
      +
      +  voice:
      +    enabled_voice_only:    "語音模式已啟用。\n當你傳送語音訊息時,我會以語音回覆。\n使用 /voice tts 讓所有訊息都收到語音回覆。"
      +    disabled_text:         "語音模式已停用。僅文字回覆。"
      +    tts_enabled:           "自動 TTS 已啟用。\n所有回覆都將包含一則語音訊息。"
      +    status_mode:           "語音模式:{label}"
      +    status_channel:        "語音頻道:#{channel}"
      +    status_participants:   "參與人數:{count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              "(正在說話)"
      +    enabled_short:         "語音模式已啟用。"
      +    disabled_short:        "語音模式已停用。"
      +    label_off:             "關閉(僅文字)"
      +    label_voice_only:      "開啟(僅對語音訊息進行語音回覆)"
      +    label_all:             "TTS(對所有訊息進行語音回覆)"
      +
      +  yolo:
      +    disabled:                   "⚠️ 本工作階段 YOLO 模式 **已關閉** — 危險指令將需要批准。"
      +    enabled:                    "⚡ 本工作階段 YOLO 模式 **已開啟** — 所有指令自動批准。請謹慎使用。"
      +
      +  shared:
      +    session_db_unavailable:      "工作階段資料庫無法使用。"
      +    session_db_unavailable_prefix: "工作階段資料庫無法使用"
      +    session_not_found:           "資料庫中找不到此工作階段。"
      +    warn_passthrough:            "⚠️ {error}"
    • locales/zh.yaml
      @@ -22,3 +22,329 @@ gateway:
         no_active_goal:   "当前没有活跃的目标。"
         config_read_failed: "⚠️ 无法读取 config.yaml:{error}"
         config_save_failed: "⚠️ 无法保存配置:{error}"
      +
      +  model:
      +    error_prefix:           "错误:{error}"
      +    switched:               "已切换模型为 `{model}`"
      +    provider_label:         "提供方:{provider}"
      +    context_label:          "上下文:{tokens} tokens"
      +    max_output_label:       "最大输出:{tokens} tokens"
      +    cost_label:             "费用:{cost}"
      +    capabilities_label:     "能力:{capabilities}"
      +    prompt_caching_enabled: "提示词缓存:已启用"
      +    warning_prefix:         "警告:{warning}"
      +    saved_global:           "已保存到 config.yaml(`--global`)"
      +    session_only_hint:      "_(仅本次会话有效 — 添加 `--global` 可永久保存)_"
      +    current_label:          "当前:`{model}`({provider})"
      +    current_tag:            "(当前)"
      +    more_models_suffix:     "(还有 {count} 个)"
      +    usage_switch_model:     "`/model <name>` — 切换模型"
      +    usage_switch_provider:  "`/model <name> --provider <slug>` — 切换提供方"
      +    usage_persist:          "`/model <name> --global` — 永久保存"
      +
      +  agents:
      +    header:                "🤖 **活跃代理与任务**"
      +    active_agents:         "**活跃代理:** {count}"
      +    this_chat:             " · 当前聊天"
      +    more:                  "... 还有 {count} 个"
      +    running_processes:     "**运行中的后台进程:** {count}"
      +    async_jobs:            "**网关异步任务:** {count}"
      +    none:                  "没有活跃的代理或运行中的任务。"
      +    state_starting:        "启动中"
      +    state_running:         "运行中"
      +
      +  approve:
      +    no_pending:            "没有待批准的命令。"
      +    once_singular:         "✅ 命令已批准。代理正在恢复…"
      +    once_plural:           "✅ 命令已批准({count} 条命令)。代理正在恢复…"
      +    session_singular:      "✅ 命令已批准(本次会话内允许该模式)。代理正在恢复…"
      +    session_plural:        "✅ 命令已批准(本次会话内允许该模式)({count} 条命令)。代理正在恢复…"
      +    always_singular:       "✅ 命令已批准(永久允许该模式)。代理正在恢复…"
      +    always_plural:         "✅ 命令已批准(永久允许该模式)({count} 条命令)。代理正在恢复…"
      +
      +  background:
      +    usage:                 "用法:/background <提示>\n示例:/background 总结今天 HN 上热门的故事\n\n在独立会话中运行该提示。你可以继续聊天 — 结果完成后将在此显示。"
      +    started:               "🔄 后台任务已启动:「{preview}」\n任务 ID:{task_id}\n你可以继续聊天 — 完成后结果将在此显示。"
      +
      +  branch:
      +    db_unavailable:        "会话数据库不可用。"
      +    no_conversation:       "没有可分支的对话 — 请先发送一条消息。"
      +    create_failed:         "创建分支失败:{error}"
      +    switch_failed:         "分支已创建,但无法切换到它。"
      +    branched_one:          "⑂ 已分支到 **{title}**(已复制 {count} 条消息)\n原始:`{parent}`\n分支:`{new}`\n使用 `/resume` 切换回原始会话。"
      +    branched_many:         "⑂ 已分支到 **{title}**(已复制 {count} 条消息)\n原始:`{parent}`\n分支:`{new}`\n使用 `/resume` 切换回原始会话。"
      +
      +  commands:
      +    usage:                 "用法:`/commands [page]`"
      +    skill_header:          "⚡ **技能命令**:"
      +    default_desc:          "技能命令"
      +    none:                  "没有可用的命令。"
      +    header:                "📚 **命令**(共 {total} 个,第 {page}/{total_pages} 页)"
      +    nav_prev:              "`/commands {page}` ← 上一页"
      +    nav_next:              "下一页 → `/commands {page}`"
      +    out_of_range:          "_(请求的第 {requested} 页超出范围,显示第 {page} 页。)_"
      +
      +  compress:
      +    not_enough:            "对话内容不足,无法压缩(至少需要 4 条消息)。"
      +    no_provider:           "未配置提供方 — 无法压缩。"
      +    nothing_to_do:         "暂无可压缩内容(对话记录仍全部为受保护上下文)。"
      +    focus_line:            "聚焦:\"{topic}\""
      +    summary_failed:        "⚠️ 摘要生成失败({error})。{count} 条历史消息已被移除并替换为占位符;之前的上下文已无法恢复。建议检查 auxiliary.compression 模型配置。"
      +    aux_failed:            "ℹ️ 配置的压缩模型 `{model}` 失败({error})。已使用主模型恢复 — 上下文完好 — 但您可能想检查 config.yaml 中的 `auxiliary.compression.model`。"
      +    failed:                "压缩失败:{error}"
      +
      +  debug:
      +    upload_failed:         "✗ 无法上传调试报告:{error}"
      +    header:                "**调试报告已上传:**"
      +    auto_delete:           "⏱ 粘贴内容将在 6 小时后自动删除。"
      +    full_logs_hint:        "如需上传完整日志,请在 CLI 中使用 `hermes debug share`。"
      +    share_hint:            "请将这些链接分享给 Hermes 团队以获得支持。"
      +
      +  deny:
      +    stale:                 "❌ 命令已拒绝(批准已过期)。"
      +    no_pending:            "没有待拒绝的命令。"
      +    denied_singular:       "❌ 命令已拒绝。"
      +    denied_plural:         "❌ 命令已拒绝({count} 条命令)。"
      +
      +  fast:
      +    not_supported:         "⚡ /fast 仅适用于支持优先处理(Priority Processing)的 OpenAI 模型。"
      +    status:                "⚡ 优先处理\n\n当前模式:`{mode}`\n\n_用法:_ `/fast <normal|fast|status>`"
      +    unknown_arg:           "⚠️ 未知参数:`{arg}`\n\n**有效选项:** normal、fast、status"
      +    saved:                 "⚡ ✓ 优先处理:**{label}**(已保存到配置)\n_(下一条消息生效)_"
      +    session_only:          "⚡ ✓ 优先处理:**{label}**(仅本次会话)"
      +    label_fast:            "FAST"
      +    label_normal:          "NORMAL"
      +    status_fast:           "fast"
      +    status_normal:         "normal"
      +
      +  footer:
      +    status:                "📎 运行时页脚:**{state}**\n字段:`{fields}`\n平台:`{platform}`"
      +    usage:                 "用法:`/footer [on|off|status]`"
      +    saved:                 "📎 运行时页脚:**{state}**{example}\n_(已全局保存 — 下一条消息生效)_"
      +    example_line:          "\n示例:`{preview}`"
      +    state_on:              "ON"
      +    state_off:             "OFF"
      +
      +  goal:
      +    unavailable:           "此会话不支持目标功能。"
      +    no_goal_set:           "未设置目标。"
      +    paused:                "⏸ 目标已暂停:{goal}"
      +    no_resume:             "没有可恢复的目标。"
      +    resumed:               "▶ 目标已恢复:{goal}\n发送任意消息继续,或等待 — 我会在下一轮继续推进。"
      +    invalid:               "无效目标:{error}"
      +    set:                   "⊙ 目标已设置({budget} 轮预算):{goal}\n我将持续工作直到目标完成、你暂停/清除它,或预算耗尽。\n控制命令:/goal status · /goal pause · /goal resume · /goal clear"
      +
      +  help:
      +    header:                "📖 **Hermes 命令**\n"
      +    skill_header:          "\n⚡ **技能命令**({count} 个活跃):"
      +    more_use_commands:     "\n... 还有 {count} 个。使用 `/commands` 查看完整分页列表。"
      +
      +  insights:
      +    invalid_days:          "无效的 --days 值:{value}"
      +    error:                 "生成洞察时出错:{error}"
      +
      +  kanban:
      +    error_prefix:          "⚠ kanban 错误:{error}"
      +    subscribed_suffix:     "(已订阅 — 当 {task_id} 完成或被阻塞时将通知您)"
      +    truncated_suffix:      "…(已截断;如需完整输出请在终端运行 `hermes kanban …`)"
      +    no_output:             "(无输出)"
      +
      +  personality:
      +    none_configured:       "`{path}/config.yaml` 中未配置人格设定"
      +    header:                "🎭 **可用人格**\n"
      +    none_option:           "• `none` — (不应用人格覆盖)"
      +    item:                  "• `{name}` — {preview}"
      +    usage:                 "\n用法:`/personality <name>`"
      +    save_failed:           "⚠️ 保存人格变更失败:{error}"
      +    cleared:               "🎭 已清除人格 — 使用基础代理行为。\n_(在下一条消息时生效)_"
      +    set_to:                "🎭 人格已设置为 **{name}**\n_(在下一条消息时生效)_"
      +    unknown:               "未知人格:`{name}`\n\n可用:{available}"
      +
      +  profile:
      +    header:                "👤 **配置文件:** `{profile}`"
      +    home:                  "📂 **主目录:** `{home}`"
      +
      +  reasoning:
      +    level_default:             "medium(默认)"
      +    level_disabled:            "none(已禁用)"
      +    scope_session:             "会话覆盖"
      +    scope_global:              "全局配置"
      +    status:                    "🧠 **推理设置**\n\n**强度:** `{level}`\n**作用域:** {scope}\n**显示:** {display}\n\n_用法:_ `/reasoning <none|minimal|low|medium|high|xhigh|reset|show|hide> [--global]`"
      +    display_on:                "开 ✓"
      +    display_off:               "关"
      +    display_set_on:            "🧠 ✓ 推理显示:**开启**\n在 **{platform}** 上每次响应前将显示模型的思考过程。"
      +    display_set_off:           "🧠 ✓ **{platform}** 上的推理显示:**关闭**"
      +    reset_global_unsupported:  "⚠️ 不支持 `/reasoning reset --global`。请使用 `/reasoning <level> --global` 修改全局默认值。"
      +    reset_done:                "🧠 ✓ 已清除本会话的推理覆盖;回退到全局配置。"
      +    unknown_arg:               "⚠️ 未知参数:`{arg}`\n\n**有效级别:** none, minimal, low, medium, high, xhigh\n**显示:** show, hide\n**持久化:** 添加 `--global` 以跨会话保存"
      +    set_global:                "🧠 ✓ 推理强度已设置为 `{effort}`(已保存到配置)\n_(下一条消息生效)_"
      +    set_global_save_failed:    "🧠 ✓ 推理强度已设置为 `{effort}`(仅本会话 — 配置保存失败)\n_(下一条消息生效)_"
      +    set_session:               "🧠 ✓ 推理强度已设置为 `{effort}`(仅本会话 — 添加 `--global` 以持久化)\n_(下一条消息生效)_"
      +
      +  reload_mcp:
      +    cancelled:             "🟡 已取消 /reload-mcp。MCP 工具未更改。"
      +    always_followup:       "ℹ️ 后续 `/reload-mcp` 调用将不再确认。可在 `config.yaml` 中将 `approvals.mcp_reload_confirm: true` 重新启用。"
      +    confirm_prompt:        "⚠️ **确认 /reload-mcp**\n\n重新加载 MCP 服务器会为本会话重建工具集,并**使提供方提示词缓存失效** — 下一条消息将重新发送完整输入令牌。在长上下文或高推理模型上,这可能开销较大。\n\n请选择:\n• **批准一次** — 立即重新加载\n• **始终批准** — 立即重新加载并永久静默此提示\n• **取消** — 保持 MCP 工具不变\n\n_文本备用:回复 `/approve`、`/always` 或 `/cancel`。_"
      +    header:                "🔄 **MCP 服务器已重新加载**\n"
      +    reconnected:           "♻️ 已重新连接:{names}"
      +    added:                 "➕ 已添加:{names}"
      +    removed:               "➖ 已移除:{names}"
      +    none_connected:        "没有连接的 MCP 服务器。"
      +    tools_available:       "\n🔧 来自 {servers} 个服务器的 {tools} 个工具可用"
      +    failed:                "❌ MCP 重新加载失败:{error}"
      +
      +  reload_skills:
      +    header:                "🔄 **技能已重新加载**\n"
      +    no_new:                "未检测到新技能。"
      +    total:                 "\n📚 {count} 个技能可用"
      +    added_header:          "➕ **新增技能:**"
      +    removed_header:        "➖ **移除技能:**"
      +    item_with_desc:        "    - {name}:{desc}"
      +    item_no_desc:          "    - {name}"
      +    failed:                "❌ 技能重新加载失败:{error}"
      +
      +  reset:
      +    header_default:        "✨ 会话已重置!重新开始。"
      +    header_new:            "✨ 新会话已启动!"
      +    header_titled:         "✨ 新会话已启动:{title}"
      +    title_rejected:        "\n⚠️ 标题被拒绝:{error}"
      +    title_error_untitled:  "\n⚠️ {error} — 会话以未命名方式启动。"
      +    title_empty_untitled:  "\n⚠️ 清理后标题为空 — 会话以未命名方式启动。"
      +    tip:                   "\n✦ 提示:{tip}"
      +
      +  restart:
      +    in_progress:           "⏳ 网关重启已在进行中……"
      +    restarting:            "♻ 正在重启网关。如果 60 秒内没有收到通知,请在控制台运行 `hermes gateway restart` 重启。"
      +
      +  resume:
      +    db_unavailable:        "会话数据库不可用。"
      +    no_named_sessions:     "未找到已命名的会话。\n使用 `/title 我的会话` 为当前会话命名,然后用 `/resume 我的会话` 返回。"
      +    list_header:           "📋 **已命名会话**\n"
      +    list_item:             "• **{title}**{preview_part}"
      +    list_preview_suffix:   " — _{preview}_"
      +    list_footer:           "\n用法:`/resume <会话名称>`"
      +    list_failed:           "无法列出会话:{error}"
      +    not_found:             "未找到匹配 '**{name}**' 的会话。\n使用不带参数的 `/resume` 查看可用会话。"
      +    already_on:            "📌 已在会话 **{name}** 上。"
      +    switch_failed:         "切换会话失败。"
      +    resumed_one:           "↻ 已恢复会话 **{title}**({count} 条消息)。对话已还原。"
      +    resumed_many:          "↻ 已恢复会话 **{title}**({count} 条消息)。对话已还原。"
      +    resumed_no_count:      "↻ 已恢复会话 **{title}**。对话已还原。"
      +
      +  retry:
      +    no_previous:           "没有可重试的上一条消息。"
      +
      +  rollback:
      +    not_enabled:           "检查点未启用。\n请在 config.yaml 中启用:\n```\ncheckpoints:\n  enabled: true\n```"
      +    none_found:            "未找到 {cwd} 的检查点"
      +    invalid_number:        "无效的检查点编号。请使用 1-{max}。"
      +    restored:              "✅ 已恢复到检查点 {hash}:{reason}\n已自动保存回滚前的快照。"
      +    restore_failed:        "❌ {error}"
      +
      +  set_home:
      +    save_failed:           "无法保存主频道:{error}"
      +    success:               "✅ 主频道已设置为 **{name}**(ID:{chat_id})。\n定时任务和跨平台消息将发送到此处。"
      +
      +  status:
      +    header:                "📊 **Hermes 网关状态**"
      +    session_id:            "**会话 ID:** `{session_id}`"
      +    title:                 "**标题:** {title}"
      +    created:               "**创建时间:** {timestamp}"
      +    last_activity:         "**最近活动:** {timestamp}"
      +    tokens:                "[redacted:secret-assignment] 数:** {tokens}"
      +    agent_running:         "**代理运行中:** {state}"
      +    state_yes:             "是 ⚡"
      +    state_no:              "否"
      +    queued:                "**排队的后续:** {count}"
      +    platforms:             "**已连接平台:** {platforms}"
      +
      +  stop:
      +    stopped_pending:       "⚡ 已停止。代理尚未启动 — 你可以继续此会话。"
      +    stopped:               "⚡ 已停止。你可以继续此会话。"
      +    no_active:             "没有可停止的活跃任务。"
      +
      +  title:
      +    db_unavailable:        "会话数据库不可用。"
      +    warn_prefix:           "⚠️ {error}"
      +    empty_after_clean:     "⚠️ 清理后标题为空。请使用可打印字符。"
      +    set_to:                "✏️ 已设置会话标题:**{title}**"
      +    not_found:             "未在数据库中找到该会话。"
      +    current_with_title:    "📌 会话:`{session_id}`\n标题:**{title}**"
      +    current_no_title:      "📌 会话:`{session_id}`\n尚未设置标题。用法:`/title 我的会话名称`"
      +
      +  topic:
      +    not_telegram_dm:         "/topic 命令仅在 Telegram 私聊中可用。"
      +    no_session_db:           "会话数据库不可用。"
      +    unauthorized:            "您无权在此 bot 上使用 /topic。"
      +    restore_needs_topic:     "若要恢复会话,请先创建或打开一个 Telegram topic,然后在该 topic 中发送 /topic <session-id>。要创建新 topic,请打开 All Messages 并在其中发送任意消息。"
      +    topics_disabled:         "此 bot 尚未启用 Telegram topics。\n\n启用方法:\n1. 打开 @BotFather。\n2. 选择您的 bot。\n3. 打开 Bot Settings → Threads Settings。\n4. 开启 Threaded Mode,并确保允许用户创建新线程。\n\n然后再次发送 /topic。"
      +    topics_user_disallowed:  "Telegram topics 已启用,但不允许用户创建 topics。\n\n打开 @BotFather → 选择您的 bot → Bot Settings → Threads Settings,然后关闭 'Disallow users to create new threads'。\n\n然后再次发送 /topic。"
      +    enable_failed:           "启用 Telegram topic 模式失败:{error}"
      +    bound_status:            "此 topic 已关联到:\n会话:{label}\nID:{session_id}\n\n使用 /new 将此 topic 替换为新会话。\n如需并行工作,请打开 All Messages 并在其中发送消息以创建另一个 topic。"
      +    thread_ready:            "Telegram 多会话 topics 已启用。\n\n此 topic 将作为独立的 Hermes 会话使用。使用 /new 替换此 topic 的当前会话。如需并行工作,请打开 All Messages 并在其中发送消息以创建另一个 topic。"
      +    untitled_session:        "未命名会话"
      +
      +  undo:
      +    nothing:               "没有可撤销的内容。"
      +    removed:               "↩️ 已撤销 {count} 条消息。\n已移除:「{preview}」"
      +
      +  update:
      +    platform_not_messaging:  "✗ /update 仅在消息平台可用。请在终端运行 `hermes update`。"
      +    not_git_repo:            "✗ 不是 git 仓库 — 无法更新。"
      +    hermes_cmd_not_found:    "✗ 无法找到 `hermes` 命令。Hermes 正在运行,但更新命令无法在 PATH 上或通过当前 Python 解释器找到可执行文件。请尝试在终端中手动运行 `hermes update`。"
      +    start_failed:            "✗ 启动更新失败:{error}"
      +    starting:                "⚕ 正在启动 Hermes 更新…… 进度将在此处显示。"
      +
      +  usage:
      +    rate_limits:              "⏱️ **速率限制:** {state}"
      +    header_session:           "📊 **会话令牌使用情况**"
      +    label_model:              "模型:`{model}`"
      +    label_input_tokens:       "[redacted:secret-assignment]"
      +    label_cache_read:         "缓存读取令牌:{count}"
      +    label_cache_write:        "缓存写入令牌:{count}"
      +    label_output_tokens:      "[redacted:secret-assignment]"
      +    label_total:              "总计:{count}"
      +    label_api_calls:          "API 调用次数:{count}"
      +    label_cost:               "费用:{prefix}${amount}"
      +    label_cost_included:      "费用:已包含"
      +    label_context:            "上下文:{used} / {total}({pct}%)"
      +    label_compressions:       "压缩次数:{count}"
      +    header_session_info:      "📊 **会话信息**"
      +    label_messages:           "消息数:{count}"
      +    label_estimated_context:  "估计上下文:~{count} 个令牌"
      +    detailed_after_first:     "_(首次代理响应后可查看详细使用情况)_"
      +    no_data:                  "此会话暂无使用数据。"
      +
      +  verbose:
      +    not_enabled:           "`/verbose` 命令未在消息平台启用。\n\n请在 `config.yaml` 中启用:\n```yaml\ndisplay:\n  tool_progress_command: true\n```"
      +    mode_off:              "⚙️ 工具进度:**OFF** — 不显示任何工具活动。"
      +    mode_new:              "⚙️ 工具进度:**NEW** — 工具变化时显示(预览长度:`display.tool_preview_length`,默认 40)。"
      +    mode_all:              "⚙️ 工具进度:**ALL** — 显示每次工具调用(预览长度:`display.tool_preview_length`,默认 40)。"
      +    mode_verbose:          "⚙️ 工具进度:**VERBOSE** — 显示每次工具调用及完整参数。"
      +    saved_suffix:          "_(已为 **{platform}** 保存 — 下一条消息生效)_"
      +    save_failed:           "_(无法保存到配置:{error})_"
      +
      +  voice:
      +    enabled_voice_only:    "语音模式已启用。\n当你发送语音消息时,我会用语音回复。\n使用 /voice tts 让所有消息都收到语音回复。"
      +    disabled_text:         "语音模式已禁用。仅文本回复。"
      +    tts_enabled:           "自动 TTS 已启用。\n所有回复都将包含一条语音消息。"
      +    status_mode:           "语音模式:{label}"
      +    status_channel:        "语音频道:#{channel}"
      +    status_participants:   "参与人数:{count}"
      +    status_member:         "  - {name}{status}"
      +    speaking:              "(正在说话)"
      +    enabled_short:         "语音模式已启用。"
      +    disabled_short:        "语音模式已禁用。"
      +    label_off:             "关闭(仅文本)"
      +    label_voice_only:      "开启(仅对语音消息进行语音回复)"
      +    label_all:             "TTS(对所有消息进行语音回复)"
      +
      +  yolo:
      +    disabled:                   "⚠️ 本会话 YOLO 模式 **已关闭** — 危险命令将需要批准。"
      +    enabled:                    "⚡ 本会话 YOLO 模式 **已开启** — 所有命令自动批准。请谨慎使用。"
      +
      +  shared:
      +    session_db_unavailable:      "会话数据库不可用。"
      +    session_db_unavailable_prefix: "会话数据库不可用"
      +    session_not_found:           "数据库中未找到该会话。"
      +    warn_passthrough:            "⚠️ {error}"
    • plugins/hermes-achievements/dashboard/dist/index.js
      @@ -12,6 +12,35 @@
         const hooks = SDK.hooks;
         const C = SDK.components;
         const cn = SDK.utils.cn;
      +  // useI18n is a hook so each component that needs translations calls it
      +  // locally (see AchievementsPage, AchievementCard, ShareDialog, LoadingPage).
      +  // Older host dashboards may not expose useI18n yet; fall back to a no-op
      +  // shim that returns en values so the bundle still renders against an older
      +  // host SDK.  English fallback strings live alongside each call site.
      +  const useI18n = SDK.useI18n || function () { return { t: { achievements: null }, locale: "en" }; };
      +
      +  // Resolve a translation by dotted path (e.g. "card.share_text"); fall back to
      +  // the English string passed in.  Used inside components after they call
      +  // useI18n() so they can still render against an older host SDK that doesn't
      +  // expose the achievements namespace yet.
      +  function tx(t, path, fallback, vars) {
      +    let node = t && t.achievements;
      +    if (node) {
      +      const parts = path.split(".");
      +      for (let i = 0; i < parts.length; i++) {
      +        if (node && typeof node === "object" && parts[i] in node) {
      +          node = node[parts[i]];
      +        } else { node = null; break; }
      +      }
      +    }
      +    let str = (typeof node === "string") ? node : fallback;
      +    if (vars) {
      +      for (const k in vars) {
      +        str = str.replace(new RegExp("\\{" + k + "\\}", "g"), vars[k]);
      +      }
      +    }
      +    return str;
      +  }
       
         const LUCIDE = {"flame":"<path d=\"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\" />","avalanche":"<path d=\"m8 3 4 8 5-5 5 15H2L8 3z\" />\n  <path d=\"M4.14 15.08c2.62-1.57 5.24-1.43 7.86.42 2.74 1.94 5.49 2 8.23.19\" />","nodes":"<rect x=\"16\" y=\"16\" width=\"6\" height=\"6\" rx=\"1\" />\n  <rect x=\"2\" y=\"16\" width=\"6\" height=\"6\" rx=\"1\" />\n  <rect x=\"9\" y=\"2\" width=\"6\" height=\"6\" rx=\"1\" />\n  <path d=\"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3\" />\n  <path d=\"M12 12V8\" />","rocket":"<path d=\"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z\" />\n  <path d=\"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z\" />\n  <path d=\"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0\" />\n  <path d=\"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5\" />","branch":"<line x1=\"6\" x2=\"6\" y1=\"3\" y2=\"15\" />\n  <circle cx=\"18\" cy=\"6\" r=\"3\" />\n  <circle cx=\"6\" cy=\"18\" r=\"3\" />\n  <path d=\"M18 9a9 9 0 0 1-9 9\" />","daemon":"<path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\" />\n  <path d=\"M21 3v5h-5\" />","clock":"<circle cx=\"12\" cy=\"12\" r=\"10\" />\n  <polyline points=\"12 6 12 12 16 14\" />","warning":"<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n  <path d=\"M12 9v4\" />\n  <path d=\"M12 17h.01\" />","wine":"<path d=\"M8 22h8\" />\n  <path d=\"M7 10h10\" />\n  <path d=\"M12 15v7\" />\n  <path d=\"M12 15a5 5 0 0 0 5-5c0-2-.5-4-2-8H9c-1.5 4-2 6-2 8a5 5 0 0 0 5 5Z\" />","scroll":"<path d=\"M15 12h-5\" />\n  <path d=\"M15 8h-5\" />\n  <path d=\"M19 17V5a2 2 0 0 0-2-2H4\" />\n  <path d=\"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3\" />","plug":"<path d=\"m19 5 3-3\" />\n  <path d=\"m2 22 3-3\" />\n  <path d=\"M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z\" />\n  <path d=\"M7.5 13.5 10 11\" />\n  <path d=\"M10.5 16.5 13 14\" />\n  <path d=\"m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z\" />","lock":"<circle cx=\"12\" cy=\"16\" r=\"1\" />\n  <rect x=\"3\" y=\"10\" width=\"18\" height=\"12\" rx=\"2\" />\n  <path d=\"M7 10V7a5 5 0 0 1 10 0v3\" />","package_skull":"<path d=\"M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14\" />\n  <path d=\"m7.5 4.27 9 5.15\" />\n  <polyline points=\"3.29 7 12 12 20.71 7\" />\n  <line x1=\"12\" x2=\"12\" y1=\"22\" y2=\"12\" />\n  <path d=\"m17 13 5 5m-5 0 5-5\" />","restart":"<path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\" />\n  <path d=\"M21 3v5h-5\" />\n  <path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\" />\n  <path d=\"M8 16H3v5\" />","key":"<path d=\"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z\" />\n  <circle cx=\"16.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\" />","colon":"<path d=\"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1\" />\n  <path d=\"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1\" />","container":"<path d=\"M22 7.7c0-.6-.4-1.2-.8-1.5l-6.3-3.9a1.72 1.72 0 0 0-1.7 0l-10.3 6c-.5.2-.9.8-.9 1.4v6.6c0 .5.4 1.2.8 1.5l6.3 3.9a1.72 1.72 0 0 0 1.7 0l10.3-6c.5-.3.9-1 .9-1.5Z\" />\n  <path d=\"M10 21.9V14L2.1 9.1\" />\n  <path d=\"m10 14 11.9-6.9\" />\n  <path d=\"M14 19.8v-8.1\" />\n  <path d=\"M18 17.5V9.4\" />","melting_clock":"<line x1=\"10\" x2=\"14\" y1=\"2\" y2=\"2\" />\n  <line x1=\"12\" x2=\"15\" y1=\"14\" y2=\"11\" />\n  <circle cx=\"12\" cy=\"14\" r=\"8\" />","pencil":"<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\" />\n  <path d=\"m15 5 4 4\" />","blueprint":"<path d=\"m12.99 6.74 1.93 3.44\" />\n  <path d=\"M19.136 12a10 10 0 0 1-14.271 0\" />\n  <path d=\"m21 21-2.16-3.84\" />\n  <path d=\"m3 21 8.02-14.26\" />\n  <circle cx=\"12\" cy=\"5\" r=\"2\" />","pixel":"<path d=\"M3 7V5a2 2 0 0 1 2-2h2\" />\n  <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" />\n  <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" />\n  <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" />\n  <path d=\"M7 12h10\" />","ship":"<path d=\"M12 10.189V14\" />\n  <path d=\"M12 2v3\" />\n  <path d=\"M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6\" />\n  <path d=\"M19.38 20A11.6 11.6 0 0 0 21 14l-8.188-3.639a2 2 0 0 0-1.624 0L3 14a11.6 11.6 0 0 0 2.81 7.76\" />\n  <path d=\"M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1\" />","spark_cursor":"<path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\" />\n  <path d=\"M20 3v4\" />\n  <path d=\"M22 5h-4\" />\n  <path d=\"M4 17v2\" />\n  <path d=\"M5 18H3\" />","needle":"<path d=\"M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z\" />","hammer_scroll":"<path d=\"m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9\" />\n  <path d=\"m18 15 4-4\" />\n  <path d=\"m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5\" />","anvil":"<path d=\"M7 10H6a4 4 0 0 1-4-4 1 1 0 0 1 1-1h4\" />\n  <path d=\"M7 5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1 7 7 0 0 1-7 7H8a1 1 0 0 1-1-1z\" />\n  <path d=\"M9 12v5\" />\n  <path d=\"M15 12v5\" />\n  <path d=\"M5 20a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3 1 1 0 0 1-1 1H6a1 1 0 0 1-1-1\" />","crystal":"<path d=\"M6 3h12l4 6-10 13L2 9Z\" />\n  <path d=\"M11 3 8 9l4 13 4-13-3-6\" />\n  <path d=\"M2 9h20\" />","palace":"<line x1=\"3\" x2=\"21\" y1=\"22\" y2=\"22\" />\n  <line x1=\"6\" x2=\"6\" y1=\"18\" y2=\"11\" />\n  <line x1=\"10\" x2=\"10\" y1=\"18\" y2=\"11\" />\n  <line x1=\"14\" x2=\"14\" y1=\"18\" y2=\"11\" />\n  <line x1=\"18\" x2=\"18\" y1=\"18\" y2=\"11\" />\n  <polygon points=\"12 2 20 7 4 7\" />","dragon":"<path d=\"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\" />","antenna":"<path d=\"M4.9 16.1C1 12.2 1 5.8 4.9 1.9\" />\n  <path d=\"M7.8 4.7a6.14 6.14 0 0 0-.8 7.5\" />\n  <circle cx=\"12\" cy=\"9\" r=\"2\" />\n  <path d=\"M16.2 4.8c2 2 2.26 5.11.8 7.47\" />\n  <path d=\"M19.1 1.9a9.96 9.96 0 0 1 0 14.1\" />\n  <path d=\"M9.5 18h5\" />\n  <path d=\"m8 22 4-11 4 11\" />","puzzle":"<path d=\"M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z\" />","rewind":"<path d=\"M9 14 4 9l5-5\" />\n  <path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />","spiral":"<path d=\"M13 16a3 3 0 0 1 2.24 5\" />\n  <path d=\"M18 12h.01\" />\n  <path d=\"M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3\" />\n  <path d=\"M20 8.54V4a2 2 0 1 0-4 0v3\" />\n  <path d=\"M7.612 12.524a3 3 0 1 0-1.6 4.3\" />","quote":"<path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\" />\n  <path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\" />","compass":"<path d=\"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z\" />\n  <circle cx=\"12\" cy=\"12\" r=\"10\" />","browser":"<circle cx=\"12\" cy=\"12\" r=\"10\" />\n  <path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\" />\n  <path d=\"M2 12h20\" />","terminal":"<polyline points=\"4 17 10 11 4 5\" />\n  <line x1=\"12\" x2=\"20\" y1=\"19\" y2=\"19\" />","wand":"<path d=\"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72\" />\n  <path d=\"m14 7 3 3\" />\n  <path d=\"M5 6v4\" />\n  <path d=\"M19 14v4\" />\n  <path d=\"M10 2v2\" />\n  <path d=\"M7 8H3\" />\n  <path d=\"M21 16h-4\" />\n  <path d=\"M11 3H9\" />","folder":"<path d=\"M10.7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4.1\" />\n  <path d=\"m21 21-1.9-1.9\" />\n  <circle cx=\"17\" cy=\"17\" r=\"3\" />","eye":"<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" />\n  <circle cx=\"12\" cy=\"12\" r=\"3\" />","wave":"<path d=\"M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2\" />","swap":"<path d=\"m17 2 4 4-4 4\" />\n  <path d=\"M3 11v-1a4 4 0 0 1 4-4h14\" />\n  <path d=\"m7 22-4-4 4-4\" />\n  <path d=\"M21 13v1a4 4 0 0 1-4 4H3\" />","router":"<rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" />\n  <path d=\"M6.01 18H6\" />\n  <path d=\"M10.01 18H10\" />\n  <path d=\"M15 10v4\" />\n  <path d=\"M17.84 7.17a4 4 0 0 0-5.66 0\" />\n  <path d=\"M20.66 4.34a8 8 0 0 0-11.31 0\" />","codex":"<path d=\"M10 9.5 8 12l2 2.5\" />\n  <path d=\"m14 9.5 2 2.5-2 2.5\" />\n  <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />","prism":"<path d=\"M6 3h12l4 6-10 13L2 9Z\" />\n  <path d=\"M11 3 8 9l4 13 4-13-3-6\" />\n  <path d=\"M2 9h20\" />","marathon":"<line x1=\"10\" x2=\"14\" y1=\"2\" y2=\"2\" />\n  <line x1=\"12\" x2=\"15\" y1=\"14\" y2=\"11\" />\n  <circle cx=\"12\" cy=\"14\" r=\"8\" />","calendar":"<path d=\"M8 2v4\" />\n  <path d=\"M16 2v4\" />\n  <rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" />\n  <path d=\"M3 10h18\" />\n  <path d=\"M8 14h.01\" />\n  <path d=\"M12 14h.01\" />\n  <path d=\"M16 14h.01\" />\n  <path d=\"M8 18h.01\" />\n  <path d=\"M12 18h.01\" />\n  <path d=\"M16 18h.01\" />","moon":"<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\" />","cache":"<ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\" />\n  <path d=\"M3 5V19A9 3 0 0 0 21 19V5\" />\n  <path d=\"M3 12A9 3 0 0 0 21 12\" />","secret":"<path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" />\n  <path d=\"M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3\" />\n  <path d=\"M12 17h.01\" />"};
       
      @@ -249,6 +278,7 @@
         }
       
         function ShareDialog({ achievement, onClose }) {
      +    const { t } = useI18n();
           const [status, setStatus] = hooks.useState("rendering"); // rendering | ready | copied | error
           const [errorMsg, setErrorMsg] = hooks.useState(null);
           const [previewUrl, setPreviewUrl] = hooks.useState(null);
      @@ -290,7 +320,7 @@
             if (!blobRef.current) return;
             try {
               if (!navigator.clipboard || !window.ClipboardItem) {
      -          throw new Error("Clipboard image copy not supported in this browser — use Download instead.");
      +          throw new Error(tx(t, "share.clipboard_unsupported", "Clipboard image copy not supported in this browser — use Download instead."));
               }
               await navigator.clipboard.write([
                 new window.ClipboardItem({ "image/png": blobRef.current }),
      @@ -308,8 +338,11 @@
           // paste in the same flow.
           function tweetText() {
             const tierPart = achievement.tier ? (achievement.tier + " tier ") : "";
      -      return "Just unlocked " + tierPart + "\"" + achievement.name + "\" in Hermes Agent ☤\n\n" +
      -        "@NousResearch · https://hermes-agent.nousresearch.com";
      +      const tmpl = tx(t, "share.tweet_text", "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤", {
      +        tier_part: tierPart,
      +        name: achievement.name,
      +      });
      +      return tmpl + "\n\n@NousResearch · https://hermes-agent.nousresearch.com";
           }
       
           function shareOnX() {
      @@ -321,36 +354,36 @@
             className: "ha-share-backdrop",
             onClick: function (e) { if (e.target === e.currentTarget) onClose(); },
           },
      -      React.createElement("div", { className: "ha-share-dialog", role: "dialog", "aria-label": "Share achievement" },
      +      React.createElement("div", { className: "ha-share-dialog", role: "dialog", "aria-label": tx(t, "share.dialog_label", "Share achievement") },
               React.createElement("div", { className: "ha-share-head" },
      -          React.createElement("strong", null, "Share: " + achievement.name),
      -          React.createElement("button", { className: "ha-share-close", onClick: onClose, "aria-label": "Close" }, "×")
      +          React.createElement("strong", null, tx(t, "share.header", "Share: {name}", { name: achievement.name })),
      +          React.createElement("button", { className: "ha-share-close", onClick: onClose, "aria-label": tx(t, "share.close", "Close") }, "×")
               ),
               React.createElement("div", { className: "ha-share-preview" },
      -          status === "rendering" && React.createElement("div", { className: "ha-share-placeholder" }, "Rendering…"),
      -          previewUrl && React.createElement("img", { src: previewUrl, alt: achievement.name + " share card" })
      +          status === "rendering" && React.createElement("div", { className: "ha-share-placeholder" }, tx(t, "share.rendering", "Rendering…")),
      +          previewUrl && React.createElement("img", { src: previewUrl, alt: tx(t, "share.card_alt", "{name} share card", { name: achievement.name }) })
               ),
      -        status === "error" && React.createElement("div", { className: "ha-share-error" }, errorMsg || "Something went wrong."),
      +        status === "error" && React.createElement("div", { className: "ha-share-error" }, errorMsg || tx(t, "share.error_generic", "Something went wrong.")),
               React.createElement("div", { className: "ha-share-actions" },
                 React.createElement("button", {
                   className: "ha-share-btn ha-share-btn-primary",
                   onClick: shareOnX,
      -            title: "Opens X with a pre-filled post",
      -          }, "Share on X"),
      +            title: tx(t, "share.x_title", "Opens X with a pre-filled post"),
      +          }, tx(t, "share.x_button", "Share on X")),
                 React.createElement("button", {
                   className: "ha-share-btn",
                   onClick: copyToClipboard,
                   disabled: status !== "ready" && status !== "copied",
      -            title: "Copy the image to paste into your post",
      -          }, status === "copied" ? "Copied ✓" : "Copy image"),
      +            title: tx(t, "share.copy_title", "Copy the image to paste into your post"),
      +          }, status === "copied" ? tx(t, "share.copied", "Copied ✓") : tx(t, "share.copy_button", "Copy image")),
                 React.createElement("button", {
                   className: "ha-share-btn",
                   onClick: download,
                   disabled: status !== "ready" && status !== "copied",
      -          }, "Download PNG")
      +          }, tx(t, "share.download_button", "Download PNG"))
               ),
               React.createElement("p", { className: "ha-share-hint" },
      -          "Share on X opens a pre-filled post in a new tab. Click Copy image first if you want the 1200×630 badge attached — X lets you paste it right into the tweet composer. Download PNG saves the file for use anywhere."
      +          tx(t, "share.hint", "Share on X opens a pre-filled post in a new tab. Click Copy image first if you want the 1200×630 badge attached — X lets you paste it right into the tweet composer. Download PNG saves the file for use anywhere.")
               )
             )
           );
      @@ -408,24 +441,32 @@
         }
       
         function LoadingPage() {
      +    const { t } = useI18n();
           return React.createElement("div", { className: "ha-page ha-page-loading" },
             React.createElement("section", { className: "ha-hero ha-loading-hero" },
               React.createElement("div", null,
      -          React.createElement("div", { className: "ha-kicker" }, "Agentic Gamerscore"),
      -          React.createElement("h1", null, "Hermes Achievements"),
      -          React.createElement("p", null, "Scanning Hermes session history. First scan can take 5–10 seconds on large histories.")
      +          React.createElement("div", { className: "ha-kicker" }, tx(t, "hero.kicker", "Agentic Gamerscore")),
      +          React.createElement("h1", null, tx(t, "hero.title", "Hermes Achievements")),
      +          React.createElement("p", null, tx(t, "hero.scan_subtitle", "Scanning Hermes session history. First scan can take 5–10 seconds on large histories."))
               ),
               React.createElement("div", { className: "ha-scan-status", role: "status", "aria-live": "polite" },
                 React.createElement("span", { className: "ha-scan-pulse", "aria-hidden": "true" }),
                 React.createElement("div", null,
      -            React.createElement("strong", null, "Building achievement profile…"),
      -            React.createElement("p", null, "Reading sessions, tool calls, model metadata, and unlock state.")
      +            React.createElement("strong", null, tx(t, "scan.building_headline", "Building achievement profile…")),
      +            React.createElement("p", null, tx(t, "scan.building_detail", "Reading sessions, tool calls, model metadata, and unlock state."))
                 )
               )
             ),
             React.createElement("div", { className: "ha-stats" },
      -        ["Unlocked", "Discovered", "Secrets", "Highest tier", "Latest"].map(function (label) {
      -          return React.createElement(C.Card, { key: label, className: "ha-stat ha-skeleton-stat" },
      +        [
      +          { key: "stats.unlocked", fallback: "Unlocked" },
      +          { key: "stats.discovered", fallback: "Discovered" },
      +          { key: "stats.secrets", fallback: "Secrets" },
      +          { key: "stats.highest_tier", fallback: "Highest tier" },
      +          { key: "stats.latest", fallback: "Latest" },
      +        ].map(function (entry) {
      +          const label = tx(t, entry.key, entry.fallback);
      +          return React.createElement(C.Card, { key: entry.key, className: "ha-stat ha-skeleton-stat" },
                   React.createElement(C.CardContent, { className: "ha-stat-content" },
                     React.createElement("div", { className: "ha-stat-label" }, label),
                     React.createElement("div", { className: "ha-skeleton ha-skeleton-stat-value" }),
      @@ -436,12 +477,12 @@
             ),
             React.createElement("section", { className: "ha-guide ha-loading-guide" },
               React.createElement("div", null,
      -          React.createElement("strong", null, "Scan status"),
      -          React.createElement("p", null, "Hermes is scanning local history once, then cards will appear automatically. Nothing is stuck if this takes a few seconds.")
      +          React.createElement("strong", null, tx(t, "guide.scan_status_header", "Scan status")),
      +          React.createElement("p", null, tx(t, "guide.scan_status_body", "Hermes is scanning local history once, then cards will appear automatically. Nothing is stuck if this takes a few seconds."))
               ),
               React.createElement("div", null,
      -          React.createElement("strong", null, "What is scanned"),
      -          React.createElement("p", null, "Sessions, tool calls, model metadata, errors, achievements, and local unlock state.")
      +          React.createElement("strong", null, tx(t, "guide.what_scanned_header", "What is scanned")),
      +          React.createElement("p", null, tx(t, "guide.what_scanned_body", "Sessions, tool calls, model metadata, errors, achievements, and local unlock state."))
               )
             ),
             React.createElement("section", { className: "ha-grid" }, [0, 1, 2, 3, 4, 5].map(function (i) {
      @@ -452,14 +493,30 @@
       
       
         function AchievementCard({ achievement }) {
      +    const { t } = useI18n();
           const unlocked = achievement.unlocked;
           const progress = achievement.progress || 0;
           const pct = achievement.progress_pct || (unlocked ? 100 : 0);
           const state = achievement.state || (unlocked ? "unlocked" : "discovered");
      -    const stateLabel = state === "unlocked" ? "Unlocked" : (state === "secret" ? "Secret" : "Discovered");
      +    const stateLabel = state === "unlocked"
      +      ? tx(t, "state.unlocked", "Unlocked")
      +      : (state === "secret" ? tx(t, "state.secret", "Secret") : tx(t, "state.discovered", "Discovered"));
           const targetTier = achievement.next_tier || achievement.tier;
      -    const tierLabel = achievement.tier ? achievement.tier : (targetTier ? "Target " + targetTier : (state === "secret" ? "Hidden" : (unlocked ? "Complete" : "Objective")));
      -    const progressText = state === "secret" ? "hidden" : (progress + (achievement.next_threshold ? " / " + achievement.next_threshold : ""));
      +    let tierLabel;
      +    if (achievement.tier) {
      +      tierLabel = achievement.tier;
      +    } else if (targetTier) {
      +      tierLabel = tx(t, "tier.target", "Target {tier}", { tier: targetTier });
      +    } else if (state === "secret") {
      +      tierLabel = tx(t, "tier.hidden", "Hidden");
      +    } else if (unlocked) {
      +      tierLabel = tx(t, "tier.complete", "Complete");
      +    } else {
      +      tierLabel = tx(t, "tier.objective", "Objective");
      +    }
      +    const progressText = state === "secret"
      +      ? tx(t, "progress.hidden", "hidden")
      +      : (progress + (achievement.next_threshold ? " / " + achievement.next_threshold : ""));
           const [shareOpen, setShareOpen] = hooks.useState(false);
           return React.createElement(C.Card, { className: cn("ha-card", "ha-state-" + state, tierClass(achievement.tier || achievement.next_tier)) },
             React.createElement(C.CardContent, { className: "ha-card-content" },
      @@ -475,21 +532,23 @@
                   state === "unlocked" && React.createElement("button", {
                     className: "ha-share-trigger",
                     onClick: function () { setShareOpen(true); },
      -              title: "Share this achievement",
      -              "aria-label": "Share " + achievement.name,
      -            }, "Share")
      +              title: tx(t, "card.share_title", "Share this achievement"),
      +              "aria-label": tx(t, "card.share_label", "Share {name}", { name: achievement.name }),
      +            }, tx(t, "card.share_text", "Share"))
                 )
               ),
               React.createElement("p", { className: "ha-description" }, achievement.description),
               achievement.criteria && React.createElement("details", { className: "ha-criteria" },
      -          React.createElement("summary", null, state === "secret" ? "How to reveal" : "What counts"),
      +          React.createElement("summary", null, state === "secret"
      +            ? tx(t, "card.how_to_reveal", "How to reveal")
      +            : tx(t, "card.what_counts", "What counts")),
                 React.createElement("p", null, achievement.criteria)
               ),
               React.createElement("div", { className: "ha-evidence-slot" },
                 achievement.evidence ? React.createElement("div", { className: "ha-evidence" },
      -            React.createElement("span", { className: "ha-evidence-label" }, "Evidence"),
      -            React.createElement("span", { className: "ha-evidence-title" }, achievement.evidence.title || achievement.evidence.session_id || "session")
      -          ) : React.createElement("div", { className: "ha-evidence ha-evidence-empty", "aria-hidden": "true" }, "No evidence yet")
      +            React.createElement("span", { className: "ha-evidence-label" }, tx(t, "card.evidence_label", "Evidence")),
      +            React.createElement("span", { className: "ha-evidence-title" }, achievement.evidence.title || achievement.evidence.session_id || tx(t, "card.evidence_session_fallback", "session"))
      +          ) : React.createElement("div", { className: "ha-evidence ha-evidence-empty", "aria-hidden": "true" }, tx(t, "card.no_evidence", "No evidence yet"))
               ),
               React.createElement("div", { className: "ha-progress-row" },
                 React.createElement("div", { className: "ha-progress-track" },
      @@ -506,6 +565,7 @@
         }
       
         function AchievementsPage() {
      +    const { t } = useI18n();
           const [data, setData] = hooks.useState(null);
           const [loading, setLoading] = hooks.useState(true);
           const [error, setError] = hooks.useState(null);
      @@ -554,7 +614,7 @@
           const discovered = achievements.filter(function (a) { return a.state === "discovered"; });
           const secret = [redacted:secret-assignment] (a) { return a.state === "secret"; });
           const latest = unlocked.slice().sort(function (a, b) { return (b.unlocked_at || 0) - (a.unlocked_at || 0); }).slice(0, 5);
      -    const highest = ["Olympian", "Diamond", "Gold", "Silver", "Copper"].find(function (tier) { return unlocked.some(function (a) { return a.tier === tier; }); }) || "None yet";
      +    const highest = ["Olympian", "Diamond", "Gold", "Silver", "Copper"].find(function (tier) { return unlocked.some(function (a) { return a.tier === tier; }); }) || tx(t, "stats.none_yet", "None yet");
       
           // Build the in-progress scan banner once so the JSX below stays readable.
           // Shows nothing when the scan is idle. When a scan is running it renders
      @@ -568,11 +628,15 @@
             const total = Number(meta.sessions_expected_total || 0);
             const pct = total > 0 ? Math.max(0, Math.min(100, Math.floor((scanned / total) * 100))) : 0;
             const headline = scanMode === "pending"
      -        ? "Starting achievement scan…"
      -        : "Building achievement profile…";
      +        ? tx(t, "scan.starting_headline", "Starting achievement scan…")
      +        : tx(t, "scan.building_headline", "Building achievement profile…");
             const detail = total > 0
      -        ? ("Scanned " + scanned.toLocaleString() + " of " + total.toLocaleString() + " sessions · " + pct + "%. Badges unlock as more history streams in.")
      -        : "Reading sessions, tool calls, model metadata, and unlock state. Badges appear here as they unlock.";
      +        ? tx(t, "scan.progress_detail", "Scanned {scanned} of {total} sessions · {pct}%. Badges unlock as more history streams in.", {
      +            scanned: scanned.toLocaleString(),
      +            total: total.toLocaleString(),
      +            pct: String(pct),
      +          })
      +        : tx(t, "scan.idle_detail", "Reading sessions, tool calls, model metadata, and unlock state. Badges appear here as they unlock.");
             scanBanner = React.createElement("section", { className: "ha-scan-banner", role: "status", "aria-live": "polite" },
               React.createElement("div", { className: "ha-scan-banner-head" },
                 React.createElement("span", { className: "ha-scan-pulse", "aria-hidden": "true" }),
      @@ -591,44 +655,57 @@
             return React.createElement(LoadingPage, null);
           }
       
      +    // Translate the "All" category pill but keep the underlying state ("All")
      +    // as the canonical key the API matches against.
      +    const allCategoryLabel = tx(t, "filters.all_categories", "All");
      +    const visibilityLabels = {
      +      all: tx(t, "filters.visibility_all", "all"),
      +      unlocked: tx(t, "filters.visibility_unlocked", "unlocked"),
      +      discovered: tx(t, "filters.visibility_discovered", "discovered"),
      +      secret: tx(t, "filters.visibility_secret", "secret"),
      +    };
      +
           return React.createElement("div", { className: "ha-page" },
             React.createElement("section", { className: "ha-hero" },
               React.createElement("div", null,
      -          React.createElement("div", { className: "ha-kicker" }, "Agentic Gamerscore"),
      -          React.createElement("h1", null, "Hermes Achievements"),
      -          React.createElement("p", null, "Collectible Hermes badges earned from real session history. Known unfinished achievements are shown as Discovered; Secret achievements stay hidden until the first matching behavior appears.")
      +          React.createElement("div", { className: "ha-kicker" }, tx(t, "hero.kicker", "Agentic Gamerscore")),
      +          React.createElement("h1", null, tx(t, "hero.title", "Hermes Achievements")),
      +          React.createElement("p", null, tx(t, "hero.subtitle", "Collectible Hermes badges earned from real session history. Known unfinished achievements are shown as Discovered; Secret achievements stay hidden until the first matching behavior appears."))
               ),
      -        React.createElement(C.Button, { onClick: load, className: "ha-refresh" }, "Rescan")
      +        React.createElement(C.Button, { onClick: load, className: "ha-refresh" }, tx(t, "actions.rescan", "Rescan"))
             ),
             scanBanner,
             error && React.createElement(C.Card, { className: "ha-error" }, React.createElement(C.CardContent, null, String(error))),
             React.createElement("div", { className: "ha-stats" },
      -        React.createElement(StatCard, { label: "Unlocked", value: (data ? data.unlocked_count : 0) + " / " + (data ? data.total_count : 0), hint: "earned badges" }),
      -        React.createElement(StatCard, { label: "Discovered", value: discovered.length, hint: "known, not earned yet" }),
      -        React.createElement(StatCard, { label: "Secrets", value: secret.length, hint: "hidden until first signal" }),
      -        React.createElement(StatCard, { label: "Highest tier", value: highest, hint: "Copper → Silver → Gold → Diamond → Olympian" }),
      -        React.createElement(StatCard, { label: "Latest", value: latest[0] ? latest[0].name : "None yet", hint: latest[0] ? latest[0].category : "run Hermes more" })
      +        React.createElement(StatCard, { label: tx(t, "stats.unlocked", "Unlocked"), value: (data ? data.unlocked_count : 0) + " / " + (data ? data.total_count : 0), hint: tx(t, "stats.unlocked_hint", "earned badges") }),
      +        React.createElement(StatCard, { label: tx(t, "stats.discovered", "Discovered"), value: discovered.length, hint: tx(t, "stats.discovered_hint", "known, not earned yet") }),
      +        React.createElement(StatCard, { label: tx(t, "stats.secrets", "Secrets"), value: secret.length, hint: tx(t, "stats.secrets_hint", "hidden until first signal") }),
      +        React.createElement(StatCard, { label: tx(t, "stats.highest_tier", "Highest tier"), value: highest, hint: tx(t, "stats.highest_tier_hint", "Copper → Silver → Gold → Diamond → Olympian") }),
      +        React.createElement(StatCard, { label: tx(t, "stats.latest", "Latest"), value: latest[0] ? latest[0].name : tx(t, "stats.none_yet", "None yet"), hint: latest[0] ? latest[0].category : tx(t, "stats.latest_hint_empty", "run Hermes more") })
             ),
             React.createElement("section", { className: "ha-guide" },
               React.createElement("div", null,
      -          React.createElement("strong", null, "Tiers"),
      +          React.createElement("strong", null, tx(t, "guide.tiers_header", "Tiers")),
                 React.createElement(TierLegend, null)
               ),
               React.createElement("div", null,
      -          React.createElement("strong", null, "Secret achievements"),
      -          React.createElement("p", null, "Secrets hide their exact trigger. Once Hermes sees a related signal, the card becomes Discovered and shows its requirement.")
      +          React.createElement("strong", null, tx(t, "guide.secret_header", "Secret achievements")),
      +          React.createElement("p", null, tx(t, "guide.secret_body", "Secrets hide their exact trigger. Once Hermes sees a related signal, the card becomes Discovered and shows its requirement."))
               )
             ),
             React.createElement("div", { className: "ha-toolbar" },
               React.createElement("div", { className: "ha-pills" }, categories.map(function (cat) {
      -          return React.createElement("button", { key: cat, onClick: function () { setCategory(cat); }, className: cat === category ? "active" : "" }, cat);
      +          // Render the localized "All" pill but keep the underlying value
      +          // unchanged so the filter logic still compares against "All".
      +          const pillLabel = cat === "All" ? allCategoryLabel : cat;
      +          return React.createElement("button", { key: cat, onClick: function () { setCategory(cat); }, className: cat === category ? "active" : "" }, pillLabel);
               })),
               React.createElement("div", { className: "ha-pills" }, ["all", "unlocked", "discovered", "secret"].map(function (v) {
      -          return React.createElement("button", { key: v, onClick: function () { setVisibility(v); }, className: v === visibility ? "active" : "" }, v);
      +          return React.createElement("button", { key: v, onClick: function () { setVisibility(v); }, className: v === visibility ? "active" : "" }, visibilityLabels[v] || v);
               }))
             ),
             latest.length > 0 && React.createElement("section", { className: "ha-latest" },
      -        React.createElement("h2", null, "Recent unlocks"),
      +        React.createElement("h2", null, tx(t, "latest.header", "Recent unlocks")),
               React.createElement("div", { className: "ha-latest-row" }, latest.map(function (a) {
                 return React.createElement("div", { key: a.id, className: cn("ha-chip", tierClass(a.tier)) },
                   React.createElement("span", { className: "ha-chip-icon" }, React.createElement(AchievementIcon, { icon: a.icon || "secret" })),
      @@ -638,8 +715,8 @@
             ),
             visibility === "secret" && visible.length === 0 && React.createElement(C.Card, { className: "ha-secret-empty" },
               React.createElement(C.CardContent, { className: "ha-secret-empty-content" },
      -          React.createElement("strong", null, "No hidden secrets left in this scan."),
      -          React.createElement("p", null, "Clue: secrets usually start from unusual failure or power-user patterns — port conflicts, permission walls, missing env vars, YAML mistakes, Docker collisions, rollback/checkpoint use, cache hits, or tiny fixes after lots of red text.")
      +          React.createElement("strong", null, tx(t, "empty.no_secrets_header", "No hidden secrets left in this scan.")),
      +          React.createElement("p", null, tx(t, "empty.no_secrets_body", "Clue: secrets usually start from unusual failure or power-user patterns — port conflicts, permission walls, missing env vars, YAML mistakes, Docker collisions, rollback/checkpoint use, cache hits, or tiny fixes after lots of red text."))
               )
             ),
             React.createElement("section", { className: "ha-grid" }, visible.map(function (a) {
    • plugins/kanban/dashboard/dist/index.js
      @@ -24,9 +24,39 @@
         const { useState, useEffect, useCallback, useMemo, useRef } = SDK.hooks;
         const { cn, timeAgo } = SDK.utils;
       
      +  // useI18n is a hook each component calls locally. Older host dashboards
      +  // may not expose it yet; fall back to a shim so the bundle still renders
      +  // English against an older host SDK. English fallback strings live
      +  // alongside each call site (passed as the third arg of tx()).
      +  const useI18n = SDK.useI18n || function () { return { t: { kanban: null }, locale: "en" }; };
      +
      +  // Resolve a translation by dotted path under the kanban namespace
      +  // (e.g. "columnLabels.triage"); fall back to the English string passed in.
      +  function tx(t, path, fallback, vars) {
      +    let node = t && t.kanban;
      +    if (node) {
      +      const parts = path.split(".");
      +      for (let i = 0; i < parts.length; i++) {
      +        if (node && typeof node === "object" && parts[i] in node) {
      +          node = node[parts[i]];
      +        } else { node = null; break; }
      +      }
      +    }
      +    let str = (typeof node === "string") ? node : fallback;
      +    if (vars) {
      +      for (const k in vars) {
      +        str = str.replace(new RegExp("\\{" + k + "\\}", "g"), vars[k]);
      +      }
      +    }
      +    return str;
      +  }
      +
         // Order matches BOARD_COLUMNS in plugin_api.py.
         const COLUMN_ORDER = ["triage", "todo", "ready", "running", "blocked", "done"];
      -  const COLUMN_LABEL = {
      +  // English fallback dictionaries — used when the i18n catalog is missing
      +  // a key, and as defaults for the get*() helpers below so callers running
      +  // outside any React component (where there's no `t`) still get sane text.
      +  const FALLBACK_COLUMN_LABEL = {
           triage: "Triage",
           todo: "Todo",
           ready: "Ready",
      @@ -35,7 +65,7 @@
           done: "Done",
           archived: "Archived",
         };
      -  const COLUMN_HELP = {
      +  const FALLBACK_COLUMN_HELP = {
           triage: "Raw ideas — a specifier will flesh out the spec",
           todo: "Waiting on dependencies or unassigned",
           ready: "Assigned and waiting for a dispatcher tick",
      @@ -44,6 +74,42 @@
           done: "Completed",
           archived: "Archived",
         };
      +  const FALLBACK_DESTRUCTIVE = {
      +    done: "Mark this task as done? The worker's claim is released and dependent children become ready.",
      +    archived: "Archive this task? It disappears from the default board view.",
      +    blocked: "Mark this task as blocked? The worker's claim is released.",
      +  };
      +  const FALLBACK_DIAGNOSTIC_EVENT_LABELS = {
      +    completion_blocked_hallucination: "⚠ Completion blocked — phantom card ids",
      +    suspected_hallucinated_references: "⚠ Prose referenced phantom card ids",
      +  };
      +  const DIAGNOSTIC_EVENT_KIND_KEYS = {
      +    completion_blocked_hallucination: "completionBlockedHallucination",
      +    suspected_hallucinated_references: "suspectedHallucinatedReferences",
      +  };
      +  const DESTRUCTIVE_KEYS = {
      +    done: "confirmDone",
      +    archived: "confirmArchive",
      +    blocked: "confirmBlocked",
      +  };
      +
      +  function getColumnLabel(t, status) {
      +    return tx(t, "columnLabels." + status, FALLBACK_COLUMN_LABEL[status] || status);
      +  }
      +  function getColumnHelp(t, status) {
      +    return tx(t, "columnHelp." + status, FALLBACK_COLUMN_HELP[status] || "");
      +  }
      +  function getDestructiveConfirm(t, status) {
      +    const key = DESTRUCTIVE_KEYS[status];
      +    if (!key) return null;
      +    return tx(t, key, FALLBACK_DESTRUCTIVE[status]);
      +  }
      +  function getDiagnosticEventLabel(t, kind) {
      +    const key = DIAGNOSTIC_EVENT_KIND_KEYS[kind];
      +    if (!key) return null;
      +    return tx(t, key, FALLBACK_DIAGNOSTIC_EVENT_LABELS[kind]);
      +  }
      +
         const COLUMN_DOT = {
           triage: "hermes-kanban-dot-triage",
           todo: "hermes-kanban-dot-todo",
      @@ -54,22 +120,8 @@
           archived: "hermes-kanban-dot-archived",
         };
       
      -  const DESTRUCTIVE_TRANSITIONS = {
      -    done: "Mark this task as done? The worker's claim is released and dependent children become ready.",
      -    archived: "Archive this task? It disappears from the default board view.",
      -    blocked: "Mark this task as blocked? The worker's claim is released.",
      -  };
      -
      -  // Diagnostic kind labels for the events-tab callout. Event kinds emitted
      -  // by the kernel get a human-readable header when we detect them in the
      -  // events list; add new entries here as new diagnostic event kinds land.
      -  const DIAGNOSTIC_EVENT_LABELS = {
      -    completion_blocked_hallucination: "⚠ Completion blocked — phantom card ids",
      -    suspected_hallucinated_references: "⚠ Prose referenced phantom card ids",
      -  };
      -
         function isDiagnosticEvent(kind) {
      -    return Object.prototype.hasOwnProperty.call(DIAGNOSTIC_EVENT_LABELS, kind);
      +    return Object.prototype.hasOwnProperty.call(FALLBACK_DIAGNOSTIC_EVENT_LABELS, kind);
         }
       
         function phantomIdsFromEvent(ev) {
      @@ -78,17 +130,22 @@
           return p.phantom_cards || p.phantom_refs || [];
         }
       
      -  function withCompletionSummary(patch, count) {
      +  // Takes an optional `t` so the prompt/alert text is localised. Callers
      +  // outside React components can pass null and fall through to English.
      +  function withCompletionSummary(patch, count, t) {
           if (!patch || patch.status !== "done") return patch;
           const label = count && count > 1 ? `${count} selected task(s)` : "this task";
           const value = window.prompt(
      -      `Completion summary for ${label}. This is stored as the task result.`,
      +      tx(t, "completionSummary",
      +        "Completion summary for {label}. This is stored as the task result.",
      +        { label: label }),
             "",
           );
           if (value === null) return null;
           const summary = value.trim();
           if (!summary) {
      -      window.alert("Completion summary is required before marking a task done.");
      +      window.alert(tx(t, "completionSummaryRequired",
      +        "Completion summary is required before marking a task done."));
             return null;
           }
           return Object.assign({}, patch, { result: summary, summary });
      @@ -314,6 +371,24 @@
         // Error boundary
         // -------------------------------------------------------------------------
       
      +  // Wrap the boundary's fallback in a tiny function component so we can
      +  // call useI18n() — class components can't use hooks directly.
      +  function ErrorBoundaryFallback(props) {
      +    const { t } = useI18n();
      +    return h(Card, null,
      +      h(CardContent, { className: "p-6 text-sm" },
      +        h("div", { className: "text-destructive font-semibold mb-1" },
      +          tx(t, "renderingError", "Kanban tab hit a rendering error")),
      +        h("div", { className: "text-muted-foreground text-xs mb-3" },
      +          props.message),
      +        h(Button, {
      +          onClick: props.onReset,
      +          size: "sm",
      +        }, tx(t, "reloadView", "Reload view")),
      +      ),
      +    );
      +  }
      +
         class ErrorBoundary extends React.Component {
           constructor(props) { super(props); this.state = { error: null }; }
           static getDerivedStateFromError(error) { return { error }; }
      @@ -323,18 +398,10 @@
           }
           render() {
             if (this.state.error) {
      -        return h(Card, null,
      -          h(CardContent, { className: "p-6 text-sm" },
      -            h("div", { className: "text-destructive font-semibold mb-1" },
      -              "Kanban tab hit a rendering error"),
      -            h("div", { className: "text-muted-foreground text-xs mb-3" },
      -              String(this.state.error && this.state.error.message || this.state.error)),
      -            h(Button, {
      -              onClick: () => this.setState({ error: null }),
      -              size: "sm",
      -            }, "Reload view"),
      -          ),
      -        );
      +        return h(ErrorBoundaryFallback, {
      +          message: String(this.state.error && this.state.error.message || this.state.error),
      +          onReset: () => this.setState({ error: null }),
      +        });
             }
             return this.props.children;
           }
      @@ -345,6 +412,7 @@
         // -------------------------------------------------------------------------
       
         function KanbanPage() {
      +    const { t } = useI18n();
           const [board, setBoard] = useState(() => readSelectedBoard() || "default");
           const [boardList, setBoardList] = useState([]);      // [{slug, name, counts, ...}]
           const [showNewBoard, setShowNewBoard] = useState(false);
      @@ -497,7 +565,8 @@
               ws.onclose = function (ev) {
                 if (wsClosedRef.current) return;
                 if (ev && ev.code === 1008) {
      -            setError("WebSocket auth failed — reload the page to refresh the session token.");
      +            setError(tx(t, "wsAuthFailed",
      +              "WebSocket auth failed — reload the page to refresh the session token."));
                   return;
                 }
                 const delay = Math.min(wsBackoffRef.current, 30000);
      @@ -534,9 +603,9 @@
       
           // --- actions ------------------------------------------------------------
           const moveTask = useCallback(function (taskId, newStatus) {
      -      const confirmMsg = DESTRUCTIVE_TRANSITIONS[newStatus];
      +      const confirmMsg = getDestructiveConfirm(t, newStatus);
             if (confirmMsg && !window.confirm(confirmMsg)) return;
      -      const patch = withCompletionSummary({ status: newStatus }, 1);
      +      const patch = withCompletionSummary({ status: newStatus }, 1, t);
             if (!patch) return;
             setBoardData(function (b) {
               if (!b) return b;
      @@ -559,10 +628,10 @@
               headers: { "Content-Type": "application/json" },
               body: JSON.stringify(patch),
             }).catch(function (err) {
      -        setError(`Move failed: ${err.message || err}`);
      +        setError(tx(t, "moveFailed", "Move failed: ") + (err.message || err));
               loadBoard();
             });
      -    }, [loadBoard, board]);
      +    }, [loadBoard, board, t]);
       
           const createTask = useCallback(function (body) {
             return SDK.fetchJSON(withBoard(`${API}/tasks`, board), {
      @@ -575,13 +644,13 @@
               // the task was created successfully — but the user should know
               // their ready task will sit idle until the gateway is up.
               if (res && res.warning) {
      -          setError("Task created, but: " + res.warning);
      +          setError(tx(t, "taskCreatedWarning", "Task created, but: ") + res.warning);
               }
               loadBoard();
               loadBoardList();  // refresh counts in the switcher
               return res;
             });
      -    }, [loadBoard, loadBoardList, board]);
      +    }, [loadBoard, loadBoardList, board, t]);
       
           const toggleSelected = useCallback(function (id, additive) {
             setSelectedIds(function (prev) {
      @@ -596,7 +665,7 @@
           const applyBulk = useCallback(function (patch, confirmMsg) {
             if (selectedIds.size === 0) return;
             if (confirmMsg && !window.confirm(confirmMsg)) return;
      -      const finalPatch = withCompletionSummary(patch, selectedIds.size);
      +      const finalPatch = withCompletionSummary(patch, selectedIds.size, t);
             if (!finalPatch) return;
             const body = Object.assign({ ids: Array.from(selectedIds) }, finalPatch);
             SDK.fetchJSON(withBoard(`${API}/tasks/bulk`, board), {
      @@ -607,14 +676,15 @@
               .then(function (res) {
                 const failed = (res.results || []).filter(function (r) { return !r.ok; });
                 if (failed.length > 0) {
      -            setError(`Bulk: ${failed.length} of ${res.results.length} failed: ` +
      +            setError(tx(t, "bulkFailed", "Bulk: ") +
      +              `${failed.length} of ${res.results.length} failed: ` +
                     failed.slice(0, 3).map(function (f) { return `${f.id} (${f.error})`; }).join("; "));
                 }
                 clearSelected();
                 loadBoard();
               })
               .catch(function (e) { setError(String(e.message || e)); });
      -    }, [selectedIds, loadBoard, clearSelected, board]);
      +    }, [selectedIds, loadBoard, clearSelected, board, t]);
       
           // --- board switching ----------------------------------------------------
           const switchBoard = useCallback(function (nextSlug) {
      @@ -655,15 +725,16 @@
           // --- render -------------------------------------------------------------
           if (loading && !boardData) {
             return h("div", { className: "p-8 text-sm text-muted-foreground" },
      -        "Loading Kanban board…");
      +        tx(t, "loading", "Loading Kanban board…"));
           }
           if (error && !boardData) {
             return h(Card, null,
               h(CardContent, { className: "p-6" },
                 h("div", { className: "text-sm text-destructive" },
      -            "Failed to load Kanban board: ", error),
      +            tx(t, "loadFailed", "Failed to load Kanban board: "), error),
                 h("div", { className: "text-xs text-muted-foreground mt-2" },
      -            "The backend auto-creates kanban.db on first read. If this persists, check the dashboard logs."),
      +            tx(t, "loadFailedHint",
      +              "The backend auto-creates kanban.db on first read. If this persists, check the dashboard logs.")),
               ),
             );
           }
      @@ -771,6 +842,7 @@
         }
       
         function AttentionStrip(props) {
      +    const { t } = useI18n();
           const [expanded, setExpanded] = useState(false);
           const [dismissed, setDismissed] = useState(false);
           const diagTasks = useMemo(
      @@ -780,8 +852,8 @@
           if (dismissed || diagTasks.length === 0) return null;
           // Pick the highest severity present so we can colour the strip.
           let topSev = "warning";
      -    for (const t of diagTasks) {
      -      const s = (t.warnings && t.warnings.highest_severity) || "warning";
      +    for (const td of diagTasks) {
      +      const s = (td.warnings && td.warnings.highest_severity) || "warning";
             if (s === "critical") { topSev = "critical"; break; }
             if (s === "error" && topSev !== "critical") topSev = "error";
           }
      @@ -796,14 +868,15 @@
                 topSev === "critical" ? "!!!" : topSev === "error" ? "!!" : "⚠"),
               h("span", { className: "hermes-kanban-attention-text" },
                 diagTasks.length === 1
      -            ? "1 task needs attention"
      -            : `${diagTasks.length} tasks need attention`,
      +            ? tx(t, "taskNeedsAttention", "1 task needs attention")
      +            : tx(t, "tasksNeedAttention", "{n} tasks need attention",
      +                { n: diagTasks.length }),
               ),
               h("button", {
                 className: "hermes-kanban-attention-toggle",
                 onClick: function () { setExpanded(function (x) { return !x; }); },
                 type: "button",
      -        }, expanded ? "Hide" : "Show"),
      +        }, expanded ? tx(t, "hide", "Hide") : tx(t, "show", "Show")),
               h("button", {
                 className: "hermes-kanban-attention-dismiss",
                 onClick: function () { setDismissed(true); },
      @@ -813,11 +886,11 @@
             ),
             expanded
               ? h("div", { className: "hermes-kanban-attention-list" },
      -            diagTasks.map(function (t) {
      -              const sev = (t.warnings && t.warnings.highest_severity) || "warning";
      -              const kinds = t.warnings && t.warnings.kinds ? Object.keys(t.warnings.kinds) : [];
      +            diagTasks.map(function (task) {
      +              const sev = (task.warnings && task.warnings.highest_severity) || "warning";
      +              const kinds = task.warnings && task.warnings.kinds ? Object.keys(task.warnings.kinds) : [];
                     return h("div", {
      -                key: t.id,
      +                key: task.id,
                       className: cn(
                         "hermes-kanban-attention-row",
                         "hermes-kanban-attention-row--" + sev,
      @@ -825,19 +898,19 @@
                     },
                       h("span", { className: "hermes-kanban-attention-row-sev" },
                         sev === "critical" ? "!!!" : sev === "error" ? "!!" : "⚠"),
      -                h("span", { className: "hermes-kanban-attention-row-id" }, t.id),
      +                h("span", { className: "hermes-kanban-attention-row-id" }, task.id),
                       h("span", { className: "hermes-kanban-attention-row-title" },
      -                  t.title || "(untitled)"),
      +                  task.title || tx(t, "untitled", "(untitled)")),
                       h("span", { className: "hermes-kanban-attention-row-meta" },
      -                  t.assignee ? "@" + t.assignee : "unassigned",
      +                  task.assignee ? "@" + task.assignee : tx(t, "unassigned", "unassigned"),
                         " \u00b7 ",
      -                  kinds.length > 0 ? kinds.join(", ") : "diagnostic",
      +                  kinds.length > 0 ? kinds.join(", ") : tx(t, "diagnostic", "diagnostic"),
                       ),
                       h("button", {
                         className: "hermes-kanban-attention-row-btn",
      -                  onClick: function () { props.onOpen(t.id); },
      +                  onClick: function () { props.onOpen(task.id); },
                         type: "button",
      -                }, "Open"),
      +                }, tx(t, "open", "Open")),
                     );
                   }),
                 )
      @@ -864,6 +937,7 @@
         // -------------------------------------------------------------------------
       
         function DiagnosticActionButton(props) {
      +    const { t } = useI18n();
           const { action, onExec, busy, extra } = props;
           const label = (action.suggested ? "\u2606 " : "") + action.label;
           const cls = cn(
      @@ -885,8 +959,8 @@
               disabled: busy,
               onClick: function () { onExec(action); },
               type: "button",
      -        title: "Copy command to clipboard",
      -      }, (extra && extra.copied) ? "Copied" : label);
      +        title: tx(t, "copyCommand", "Copy command to clipboard"),
      +      }, (extra && extra.copied) ? tx(t, "copied", "Copied") : label);
           }
           if (action.kind === "comment") {
             return h("button", {
      @@ -909,6 +983,7 @@
         }
       
         function DiagnosticCard(props) {
      +    const { t } = useI18n();
           const { diag, task, boardSlug, assignees, onRefresh } = props;
           const [busy, setBusy] = useState(false);
           const [msg, setMsg] = useState(null);
      @@ -953,10 +1028,11 @@
                 headers: { "Content-Type": "application/json" },
                 body: JSON.stringify({ status: "ready" }),
               }).then(function () {
      -          setMsg({ ok: true, text: `Unblocked ${task.id}. Task is ready for the next tick.` });
      +          setMsg({ ok: true, text: tx(t, "unblockedMessage",
      +            "Unblocked {id}. Task is ready for the next tick.", { id: task.id }) });
                 if (onRefresh) onRefresh();
               }).catch(function (err) {
      -          setMsg({ ok: false, text: `Unblock failed: ${err.message || err}` });
      +          setMsg({ ok: false, text: tx(t, "unblockFailed", "Unblock failed: ") + (err.message || err) });
               }).then(function () { setBusy(false); });
               return;
             }
      @@ -968,16 +1044,17 @@
                 headers: { "Content-Type": "application/json" },
                 body: JSON.stringify({ reason: `recovery action for ${diag.kind}` }),
               }).then(function () {
      -          setMsg({ ok: true, text: `Reclaimed ${task.id}. Task is back to ready.` });
      +          setMsg({ ok: true, text: tx(t, "reclaimedMessage",
      +            "Reclaimed {id}. Task is back to ready.", { id: task.id }) });
                 if (onRefresh) onRefresh();
               }).catch(function (err) {
      -          setMsg({ ok: false, text: `Reclaim failed: ${err.message || err}` });
      +          setMsg({ ok: false, text: tx(t, "reclaimFailed", "Reclaim failed: ") + (err.message || err) });
               }).then(function () { setBusy(false); });
               return;
             }
             if (action.kind === "reassign") {
               if (!reassignProfile) {
      -          setMsg({ ok: false, text: "Pick a profile first." });
      +          setMsg({ ok: false, text: tx(t, "pickProfileFirst", "Pick a profile first.") });
                 return;
               }
               setBusy(true); setMsg(null);
      @@ -994,11 +1071,12 @@
               }).then(function () {
                 setMsg({
                   ok: true,
      -            text: `Reassigned ${task.id} to ${reassignProfile}.`,
      +            text: tx(t, "reassignedMessage", "Reassigned {id} to {profile}.",
      +              { id: task.id, profile: reassignProfile }),
                 });
                 if (onRefresh) onRefresh();
               }).catch(function (err) {
      -          setMsg({ ok: false, text: `Reassign failed: ${err.message || err}` });
      +          setMsg({ ok: false, text: tx(t, "reassignFailed", "Reassign failed: ") + (err.message || err) });
               }).then(function () { setBusy(false); });
               return;
             }
      @@ -1049,7 +1127,7 @@
             reassignAction
               ? h("div", { className: "hermes-kanban-diag-reassign-row" },
                   h("span", { className: "hermes-kanban-diag-reassign-label" },
      -              "Reassign to:"),
      +              tx(t, "reassignTo", "Reassign to:")),
                   h("select", {
                     className: "hermes-kanban-recovery-select",
                     value: reassignProfile,
      @@ -1088,6 +1166,7 @@
         }
       
         function DiagnosticsSection(props) {
      +    const { t } = useI18n();
           const diags = props.diagnostics || [];
           const hasOpenDiags = diags.length > 0;
           const [open, setOpen] = useState(hasOpenDiags);
      @@ -1104,14 +1183,14 @@
               h("span", { className: "hermes-kanban-section-head" },
                 hasOpenDiags
                   ? h("span", { className: "hermes-kanban-section-head-warning" },
      -                `\u26a0 Diagnostics (${diags.length})`)
      -            : "Diagnostics",
      +                `\u26a0 ${tx(t, "diagnostics", "Diagnostics")} (${diags.length})`)
      +            : tx(t, "diagnostics", "Diagnostics"),
               ),
               h("button", {
                 className: "hermes-kanban-section-toggle",
                 onClick: function () { setOpen(function (x) { return !x; }); },
                 type: "button",
      -        }, open ? "Hide" : "Show"),
      +        }, open ? tx(t, "hide", "Hide") : tx(t, "show", "Show")),
             ),
             open
               ? h("div", { className: "hermes-kanban-diag-list" },
      @@ -1149,6 +1228,7 @@
         }
       
         function BoardSwitcher(props) {
      +    const { t } = useI18n();
           const list = props.boardList || [];
           const current = list.find(function (b) { return b.slug === props.board; });
           const currentName = current && current.name ? current.name : props.board;
      @@ -1165,13 +1245,13 @@
           if (!shouldShow) {
             return h("div", {
               className: "hermes-kanban-boardswitcher-compact",
      -        title: "Boards let you separate unrelated streams of work",
      +        title: tx(t, "boardSwitcherHint", "Boards let you separate unrelated streams of work"),
             },
               h(Button, {
                 onClick: props.onNewClick,
                 size: "sm",
                 className: "h-7 text-xs",
      -        }, "+ New board"),
      +        }, tx(t, "newBoard", "+ New board")),
               h(DocsLink, null),
             );
           }
      @@ -1180,7 +1260,7 @@
             h("div", { className: "hermes-kanban-boardswitcher-inner" },
               h("div", { className: "flex flex-col gap-0.5" },
                 h("div", { className: "text-[11px] uppercase tracking-wider text-muted-foreground" },
      -            "Board"),
      +            tx(t, "board", "Board")),
                 h("div", { className: "flex items-center gap-2" },
                   h(Select, Object.assign({
                     value: props.board,
      @@ -1206,26 +1286,26 @@
                 size: "sm",
                 className: "h-8",
                 title: "Create a new board. Useful when you want an unrelated work stream (different project, different team, isolated scratch area).",
      -        }, "+ New board"),
      +        }, tx(t, "newBoard", "+ New board")),
               props.board !== "default"
                 ? h(Button, {
                   onClick: function () {
      -              const msg =
      -                `Archive board '${currentName}'? ` +
      -                `It will be moved to boards/_archived/ so you can recover it later. ` +
      -                `Tasks on this board will no longer appear anywhere in the UI.`;
      +              const msg = tx(t, "archiveBoardConfirm",
      +                "Archive board '{name}'? It will be moved to boards/_archived/ so you can recover it later. Tasks on this board will no longer appear anywhere in the UI.",
      +                { name: currentName });
                     if (window.confirm(msg)) props.onDeleteBoard(props.board);
                   },
                   size: "sm",
                   className: "h-8",
      -            title: "Archive this board",
      -          }, "Archive")
      +            title: tx(t, "archiveBoardTitle", "Archive this board"),
      +          }, tx(t, "archive", "Archive"))
                 : null,
             ),
           );
         }
       
         function NewBoardDialog(props) {
      +    const { t } = useI18n();
           const [slug, setSlug] = useState("");
           const [name, setName] = useState("");
           const [description, setDescription] = useState("");
      @@ -1269,14 +1349,16 @@
               className: "hermes-kanban-dialog",
               onSubmit: onSubmit,
             },
      -        h("div", { className: "hermes-kanban-dialog-title" }, "New board"),
      +        h("div", { className: "hermes-kanban-dialog-title" },
      +          tx(t, "newBoardTitle", "New board")),
               h("div", { className: "text-xs text-muted-foreground mb-2" },
      -          "Boards let you separate unrelated streams of work — one per project, repo, or domain. Workers on one board never see another board's tasks."),
      +          tx(t, "newBoardDescription",
      +            "Boards let you separate unrelated streams of work — one per project, repo, or domain. Workers on one board never see another board's tasks.")),
               h("div", { className: "flex flex-col gap-3" },
                 h("div", { className: "flex flex-col gap-1" },
      -            h(Label, { className: "text-xs" }, "Slug ",
      +            h(Label, { className: "text-xs" }, tx(t, "slug", "Slug"), " ",
                     h("span", { className: "text-muted-foreground" },
      -                "— lowercase, hyphens, e.g. atm10-server")),
      +                tx(t, "slugHint", "— lowercase, hyphens, e.g. atm10-server"))),
                   h(Input, {
                     value: slug,
                     onChange: function (e) { setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9\-_]/g, "-")); },
      @@ -1286,18 +1368,20 @@
                   }),
                 ),
                 h("div", { className: "flex flex-col gap-1" },
      -            h(Label, { className: "text-xs" }, "Display name ",
      -              h("span", { className: "text-muted-foreground" }, "(optional)")),
      +            h(Label, { className: "text-xs" }, tx(t, "displayName", "Display name"), " ",
      +              h("span", { className: "text-muted-foreground" },
      +                tx(t, "displayNameHint", "(optional)"))),
                   h(Input, {
                     value: name,
                     onChange: function (e) { setName(e.target.value); },
      -              placeholder: autoName || "Display name",
      +              placeholder: autoName || tx(t, "displayName", "Display name"),
                     className: "h-8",
                   }),
                 ),
                 h("div", { className: "flex flex-col gap-1" },
      -            h(Label, { className: "text-xs" }, "Description ",
      -              h("span", { className: "text-muted-foreground" }, "(optional)")),
      +            h(Label, { className: "text-xs" }, tx(t, "description", "Description"), " ",
      +              h("span", { className: "text-muted-foreground" },
      +                tx(t, "descriptionHint", "(optional)"))),
                   h(Input, {
                     value: description,
                     onChange: function (e) { setDescription(e.target.value); },
      @@ -1306,8 +1390,9 @@
                   }),
                 ),
                 h("div", { className: "flex flex-col gap-1" },
      -            h(Label, { className: "text-xs" }, "Icon ",
      -              h("span", { className: "text-muted-foreground" }, "(single character or emoji)")),
      +            h(Label, { className: "text-xs" }, tx(t, "icon", "Icon"), " ",
      +              h("span", { className: "text-muted-foreground" },
      +                tx(t, "iconHint", "(single character or emoji)"))),
                   h(Input, {
                     value: icon,
                     onChange: function (e) { setIcon(e.target.value.slice(0, 4)); },
      @@ -1321,7 +1406,7 @@
                     checked: switchTo,
                     onChange: function (e) { setSwitchTo(e.target.checked); },
                   }),
      -            "Switch to this board after creating it",
      +            tx(t, "switchAfterCreate", "Switch to this board after creating it"),
                 ),
               ),
               err ? h("div", { className: "text-xs text-destructive mt-2" }, err) : null,
      @@ -1331,12 +1416,12 @@
                   onClick: props.onCancel,
                   size: "sm",
                   disabled: submitting,
      -          }, "Cancel"),
      +          }, tx(t, "cancel", "Cancel")),
                 h(Button, {
                   type: "submit",
                   size: "sm",
                   disabled: submitting || !slug.trim(),
      -          }, submitting ? "Creating…" : "Create board"),
      +          }, submitting ? tx(t, "creating", "Creating…") : tx(t, "createBoard", "Create board")),
               ),
             ),
           );
      @@ -1347,14 +1432,15 @@
         // -------------------------------------------------------------------------
       
         function BoardToolbar(props) {
      +    const { t } = useI18n();
           const tenants = (props.board && props.board.tenants) || [];
           const assignees = (props.board && props.board.assignees) || [];
           return h("div", { className: "flex flex-wrap items-end gap-3" },
             h("div", { className: "flex flex-col gap-1",
                        title: "Fuzzy-match tasks by id, title, or description. Matches across all columns." },
      -        h(Label, { className: "text-xs text-muted-foreground" }, "Search"),
      +        h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "search", "Search")),
               h(Input, {
      -          placeholder: "Filter cards…",
      +          placeholder: tx(t, "filterCards", "Filter cards…"),
                 value: props.search,
                 onChange: function (e) { props.setSearch(e.target.value); },
                 className: "w-56 h-8",
      @@ -1362,25 +1448,25 @@
             ),
             h("div", { className: "flex flex-col gap-1",
                        title: "Tenants are free-form tags on a task (e.g. customer, project, team). Set them via the task drawer or kanban_create." },
      -        h(Label, { className: "text-xs text-muted-foreground" }, "Tenant"),
      +        h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "tenant", "Tenant")),
               h(Select, Object.assign({
                 value: props.tenantFilter,
                 className: "h-8",
               }, selectChangeHandler(props.setTenantFilter)),
      -          h(SelectOption, { value: "" }, "All tenants"),
      -          tenants.map(function (t) {
      -            return h(SelectOption, { key: t, value: t }, t);
      +          h(SelectOption, { value: "" }, tx(t, "allTenants", "All tenants")),
      +          tenants.map(function (tn) {
      +            return h(SelectOption, { key: tn, value: tn }, tn);
                 }),
               ),
             ),
             h("div", { className: "flex flex-col gap-1",
                        title: "Filter by assigned Hermes profile. Profiles are the named agent identities that claim and work on tasks." },
      -        h(Label, { className: "text-xs text-muted-foreground" }, "Assignee"),
      +        h(Label, { className: "text-xs text-muted-foreground" }, tx(t, "assignee", "Assignee")),
               h(Select, Object.assign({
                 value: props.assigneeFilter,
                 className: "h-8",
               }, selectChangeHandler(props.setAssigneeFilter)),
      -          h(SelectOption, { value: "" }, "All profiles"),
      +          h(SelectOption, { value: "" }, tx(t, "allProfiles", "All profiles")),
                 assignees.map(function (a) {
                   return h(SelectOption, { key: a, value: a }, a);
                 }),
      @@ -1393,7 +1479,7 @@
                 checked: props.includeArchived,
                 onChange: function (e) { props.setIncludeArchived(e.target.checked); },
               }),
      -        "Show archived",
      +        tx(t, "showArchived", "Show archived"),
             ),
             h("label", { className: "flex items-center gap-2 text-xs",
                          title: "Group the Running column by assigned profile" },
      @@ -1402,19 +1488,19 @@
                 checked: props.laneByProfile,
                 onChange: function (e) { props.setLaneByProfile(e.target.checked); },
               }),
      -        "Lanes by profile",
      +        tx(t, "lanesByProfile", "Lanes by profile"),
             ),
             h("div", { className: "flex-1" }),
             h(Button, {
               onClick: props.onNudgeDispatch,
               size: "sm",
               title: "Wake the dispatcher to claim ready tasks now instead of waiting for the next tick. Use this after adding tasks if you want them picked up immediately.",
      -      }, "Nudge dispatcher"),
      +      }, tx(t, "nudgeDispatcher", "Nudge dispatcher")),
             h(Button, {
               onClick: props.onRefresh,
               size: "sm",
               title: "Reload the board from the database. The board auto-refreshes on task events; this is for forcing a re-read.",
      -      }, "Refresh"),
      +      }, tx(t, "refresh", "Refresh")),
           );
         }
       
      @@ -1423,10 +1509,11 @@
         // -------------------------------------------------------------------------
       
         function BulkActionBar(props) {
      +    const { t } = useI18n();
           const [assignee, setAssignee] = useState("");
           return h("div", { className: "hermes-kanban-bulk" },
             h("span", { className: "hermes-kanban-bulk-count" },
      -        `${props.count} selected`),
      +        `${props.count} ${tx(t, "selected", "selected")}`),
             h(Button, {
               onClick: function () { props.onApply({ status: "ready" }); },
               size: "sm",
      @@ -1435,19 +1522,19 @@
             h(Button, {
               onClick: function () {
                 props.onApply({ status: "done" },
      -            `Mark ${props.count} task(s) as done?`);
      +            tx(t, "markDone", "Mark {n} task(s) as done?", { n: props.count }));
               },
               size: "sm",
               title: "Mark selected tasks as done. Releases any claims and unblocks dependent children. You'll be asked for a completion summary.",
      -      }, "Complete"),
      +      }, tx(t, "complete", "Complete")),
             h(Button, {
               onClick: function () {
                 props.onApply({ archive: true },
      -            `Archive ${props.count} task(s)?`);
      +            tx(t, "markArchived", "Archive {n} task(s)?", { n: props.count }));
               },
               size: "sm",
               title: "Archive selected tasks. They disappear from the default board view but remain in the database.",
      -      }, "Archive"),
      +      }, tx(t, "archive", "Archive")),
             h("div", { className: "hermes-kanban-bulk-reassign",
                        title: "Reassign selected tasks to a different Hermes profile. Pick a profile (or unassign) and click Apply." },
               h(Select, {
      @@ -1470,14 +1557,14 @@
                 disabled: !assignee,
                 size: "sm",
                 title: "Apply the selected assignee to all selected tasks.",
      -        }, "Apply"),
      +        }, tx(t, "apply", "Apply")),
             ),
             h("div", { className: "flex-1" }),
             h(Button, {
               onClick: props.onClear,
               size: "sm",
               title: "Deselect all tasks and hide this bar.",
      -      }, "Clear"),
      +      }, tx(t, "clear", "Clear")),
           );
         }
       
      @@ -1504,6 +1591,7 @@
         }
       
         function Column(props) {
      +    const { t } = useI18n();
           const [dragOver, setDragOver] = useState(false);
           const [showCreate, setShowCreate] = useState(false);
           const colRef = useRef(null);
      @@ -1537,15 +1625,18 @@
           const lanes = useMemo(function () {
             if (!props.laneByProfile || props.column.name !== "running") return null;
             const byProfile = {};
      -      for (const t of props.column.tasks) {
      -        const key = t.assignee || "(unassigned)";
      -        (byProfile[key] = byProfile[key] || []).push(t);
      +      for (const tk of props.column.tasks) {
      +        const key = tk.assignee || "(unassigned)";
      +        (byProfile[key] = byProfile[key] || []).push(tk);
             }
             return Object.keys(byProfile).sort().map(function (k) {
               return { assignee: k, tasks: byProfile[k] };
             });
           }, [props.column, props.laneByProfile]);
       
      +    const colHelp = getColumnHelp(t, props.column.name);
      +    const colLabel = getColumnLabel(t, props.column.name);
      +
           return h("div", {
             ref: colRef,
             "data-kanban-column": props.column.name,
      @@ -1558,22 +1649,22 @@
             onDrop: handleDrop,
           },
             h("div", { className: "hermes-kanban-column-header",
      -                 title: COLUMN_HELP[props.column.name] || "" },
      +                 title: colHelp || "" },
               h("span", { className: cn("hermes-kanban-dot", COLUMN_DOT[props.column.name]) }),
               h("span", { className: "hermes-kanban-column-label" },
      -          COLUMN_LABEL[props.column.name] || props.column.name),
      +          colLabel || props.column.name),
               h("span", { className: "hermes-kanban-column-count",
                           title: `${props.column.tasks.length} task${props.column.tasks.length === 1 ? "" : "s"} in this column` },
                 props.column.tasks.length),
               h("button", {
                 type: "button",
                 className: "hermes-kanban-column-add",
      -          title: "Create task in this column",
      +          title: tx(t, "createTask", "Create task in this column"),
                 onClick: function () { setShowCreate(function (v) { return !v; }); },
               }, showCreate ? "×" : "+"),
             ),
             h("div", { className: "hermes-kanban-column-sub" },
      -        COLUMN_HELP[props.column.name] || ""),
      +        colHelp || ""),
             showCreate ? h(InlineCreate, {
               columnName: props.column.name,
               allTasks: props.allTasks,
      @@ -1584,7 +1675,7 @@
             }) : null,
             h("div", { className: "hermes-kanban-column-body" },
               props.column.tasks.length === 0
      -          ? h("div", { className: "hermes-kanban-empty" }, "— no tasks —")
      +          ? h("div", { className: "hermes-kanban-empty" }, tx(t, "noTasks", "— no tasks —"))
                 : lanes
                   ? lanes.map(function (lane) {
                       return h("div", { key: lane.assignee, className: "hermes-kanban-lane" },
      @@ -1592,20 +1683,20 @@
                           h("span", { className: "hermes-kanban-lane-name" }, lane.assignee),
                           h("span", { className: "hermes-kanban-lane-count" }, lane.tasks.length),
                         ),
      -                  lane.tasks.map(function (t) {
      +                  lane.tasks.map(function (tk) {
                           return h(TaskCard, {
      -                      key: t.id, task: t,
      -                      selected: props.selectedIds.has(t.id),
      +                      key: tk.id, task: tk,
      +                      selected: props.selectedIds.has(tk.id),
                             toggleSelected: props.toggleSelected,
                             onOpen: props.onOpen,
                           });
                         }),
                       );
                     })
      -            : props.column.tasks.map(function (t) {
      +            : props.column.tasks.map(function (tk) {
                       return h(TaskCard, {
      -                  key: t.id, task: t,
      -                  selected: props.selectedIds.has(t.id),
      +                  key: tk.id, task: tk,
      +                  selected: props.selectedIds.has(tk.id),
                         toggleSelected: props.toggleSelected,
                         onOpen: props.onOpen,
                       });
      @@ -1640,6 +1731,7 @@
         }
       
         function TaskCard(props) {
      +    const { t: i18n } = useI18n();
           const t = props.task;
           const cardRef = useRef(null);
       
      @@ -1688,7 +1780,7 @@
                     checked: props.selected,
                     onChange: handleCheckbox,
                     onClick: function (e) { e.stopPropagation(); },
      -              title: "Select for bulk actions",
      +              title: tx(i18n, "selectForBulk", "Select for bulk actions"),
                   }),
                   h("span", { className: "hermes-kanban-card-id",
                               title: `Task id: ${t.id}. Use this id with kanban_show, /kanban show, or hermes kanban show.` }, t.id),
      @@ -1725,13 +1817,15 @@
                       }, `${progress.done}/${progress.total}`)
                     : null,
                 ),
      -          h("div", { className: "hermes-kanban-card-title" }, t.title || "(untitled)"),
      +          h("div", { className: "hermes-kanban-card-title" },
      +            t.title || tx(i18n, "untitled", "(untitled)")),
                 h("div", { className: "hermes-kanban-card-row hermes-kanban-card-meta" },
                   t.assignee
                     ? h("span", { className: "hermes-kanban-assignee",
                                   title: `Assigned to Hermes profile @${t.assignee}` }, "@", t.assignee)
                     : h("span", { className: "hermes-kanban-unassigned",
      -                            title: "No profile assigned. The dispatcher will pick one from available profiles when the task is Ready." }, "unassigned"),
      +                            title: "No profile assigned. The dispatcher will pick one from available profiles when the task is Ready." },
      +                  tx(i18n, "unassigned", "unassigned")),
                   t.comment_count > 0
                     ? h("span", { className: "hermes-kanban-count",
                                   title: `${t.comment_count} comment${t.comment_count === 1 ? "" : "s"} on this task` }, "💬 ", t.comment_count)
      @@ -1755,6 +1849,7 @@
         // -------------------------------------------------------------------------
       
         function InlineCreate(props) {
      +    const { t } = useI18n();
           const [title, setTitle] = useState("");
           const [assignee, setAssignee] = useState("");
           const [priority, setPriority] = useState(0);
      @@ -1799,8 +1894,9 @@
       
           const showPathInput = workspaceKind !== "scratch";
           const pathPlaceholder = workspaceKind === "dir"
      -      ? "workspace path (required, e.g. ~/projects/my-app)"
      -      : "workspace path (optional, derived from assignee if blank)";
      +      ? tx(t, "workspacePathDir", "workspace path (required, e.g. ~/projects/my-app)")
      +      : tx(t, "workspacePathOptional",
      +          "workspace path (optional, derived from assignee if blank)");
       
           return h("div", { className: "hermes-kanban-inline-create" },
             h("textarea", {
      @@ -1811,8 +1907,8 @@
                 if (e.key === "Escape") props.onCancel();
               },
               placeholder: props.columnName === "triage"
      -          ? "Rough idea — AI will spec it…"
      -          : "New task title…",
      +          ? tx(t, "triagePlaceholder", "Rough idea — AI will spec it…")
      +          : tx(t, "taskTitlePlaceholder", "New task title…"),
               autoFocus: true,
               className: "text-sm min-h-[2rem] max-h-32 resize-y w-full border border-input bg-transparent px-2 py-1 rounded-md focus:outline-none focus:ring-2 focus:ring-ring",
               rows: 2,
      @@ -1821,7 +1917,9 @@
               h(Input, {
                 value: assignee,
                 onChange: function (e) { setAssignee(e.target.value); },
      -          placeholder: props.columnName === "triage" ? "specifier" : "assignee",
      +          placeholder: props.columnName === "triage"
      +            ? tx(t, "specifier", "specifier")
      +            : tx(t, "assigneePlaceholder", "assignee"),
                 className: "h-7 text-xs flex-1",
                 title: props.columnName === "triage"
                   ? "Hermes profile that will spec this task (default: the dispatcher's configured specifier). Leave blank to let the dispatcher pick."
      @@ -1839,7 +1937,8 @@
             h(Input, {
               value: skills,
               onChange: function (e) { setSkills(e.target.value); },
      -        placeholder: "skills (optional, comma-separated): translation, github-code-review",
      +        placeholder: tx(t, "skillsPlaceholder",
      +          "skills (optional, comma-separated): translation, github-code-review"),
               title: "Force-load these skills into the worker (in addition to the built-in kanban-worker).",
               className: "h-7 text-xs",
             }),
      @@ -1867,10 +1966,10 @@
               className: "h-7 text-xs",
               title: "Optional parent task. A child stays blocked in its current column until the parent is marked done.",
             },
      -        h(SelectOption, { value: "" }, "— no parent —"),
      -        (props.allTasks || []).map(function (t) {
      -          return h(SelectOption, { key: t.id, value: t.id },
      -            `${t.id} — ${(t.title || "").slice(0, 50)}`);
      +        h(SelectOption, { value: "" }, tx(t, "noParent", "— no parent —")),
      +        (props.allTasks || []).map(function (task) {
      +          return h(SelectOption, { key: task.id, value: task.id },
      +            `${task.id} — ${(task.title || "").slice(0, 50)}`);
               }),
             ),
             h("div", { className: "flex gap-2" },
      @@ -1881,7 +1980,7 @@
               h(Button, {
                 onClick: props.onCancel,
                 size: "sm",
      -        }, "Cancel"),
      +        }, tx(t, "cancel", "Cancel")),
             ),
           );
         }
      @@ -1891,6 +1990,7 @@
         // -------------------------------------------------------------------------
       
         function TaskDrawer(props) {
      +    const { t } = useI18n();
           const [data, setData] = useState(null);
           const [loading, setLoading] = useState(true);
           const [err, setErr] = useState(null);
      @@ -2056,10 +2156,11 @@
                   type: "button",
                   onClick: props.onClose,
                   className: "hermes-kanban-drawer-close",
      -            title: "Close (Esc)",
      +            title: tx(t, "close", "Close (Esc)"),
                 }, "×"),
               ),
      -        loading ? h("div", { className: "p-4 text-sm text-muted-foreground" }, "Loading…") :
      +        loading ? h("div", { className: "p-4 text-sm text-muted-foreground" },
      +          tx(t, "loadingDetail", "Loading…")) :
               err ? h("div", { className: "p-4 text-sm text-destructive" }, err) :
               data ? h(TaskDetail, {
                 data, editing, setEditing,
      @@ -2087,19 +2188,20 @@
                       e.preventDefault(); handleComment();
                     }
                   },
      -            placeholder: "Add a comment… (Enter to submit)",
      +            placeholder: tx(t, "addComment", "Add a comment… (Enter to submit)"),
                   className: "h-8 text-sm flex-1",
                 }),
                 h(Button, {
                   onClick: handleComment,
                   size: "sm",
      -          }, "Comment"),
      +          }, tx(t, "comment", "Comment")),
               ) : null,
             ),
           );
         }
       
         function TaskDetail(props) {
      +    const { t: i18n } = useI18n();
           const t = props.data.task;
           const comments = props.data.comments || [];
           const events = props.data.events || [];
      @@ -2118,24 +2220,24 @@
                   })
                 : h("span", {
                     className: "hermes-kanban-drawer-title-text",
      -              title: "Click to edit",
      +              title: tx(i18n, "clickToEdit", "Click to edit"),
                     onClick: function () { props.setEditing(true); },
      -            }, t.title || "(untitled)"),
      +            }, t.title || tx(i18n, "untitled", "(untitled)")),
             ),
             h("div", { className: "hermes-kanban-drawer-meta" },
      -        h(MetaRow, { label: "Status", value: t.status }),
      +        h(MetaRow, { label: tx(i18n, "status", "Status"), value: t.status }),
               h(AssigneeEditor, { task: t, onPatch: props.onPatch }),
               h(PriorityEditor, { task: t, onPatch: props.onPatch }),
      -        t.tenant ? h(MetaRow, { label: "Tenant", value: t.tenant }) : null,
      +        t.tenant ? h(MetaRow, { label: tx(i18n, "tenant", "Tenant"), value: t.tenant }) : null,
               h(MetaRow, {
      -          label: "Workspace",
      +          label: tx(i18n, "workspace", "Workspace"),
                 value: `${t.workspace_kind}${t.workspace_path ? ": " + t.workspace_path : ""}`,
               }),
               (t.skills && t.skills.length > 0) ? h(MetaRow, {
      -          label: "Skills",
      +          label: tx(i18n, "skills", "Skills"),
                 value: t.skills.join(", "),
               }) : null,
      -        t.created_by ? h(MetaRow, { label: "Created by", value: t.created_by }) : null,
      +        t.created_by ? h(MetaRow, { label: tx(i18n, "createdBy", "Created by"), value: t.created_by }) : null,
             ),
             h(StatusActions, {
               task: t,
      @@ -2168,13 +2270,15 @@
               onRemoveChild: props.onRemoveChild,
             }),
             t.result ? h("div", { className: "hermes-kanban-section" },
      -        h("div", { className: "hermes-kanban-section-head" }, "Result"),
      +        h("div", { className: "hermes-kanban-section-head" }, tx(i18n, "result", "Result")),
               h(MarkdownBlock, { source: t.result, enabled: props.renderMarkdown }),
             ) : null,
             h("div", { className: "hermes-kanban-section" },
      -        h("div", { className: "hermes-kanban-section-head" }, `Comments (${comments.length})`),
      +        h("div", { className: "hermes-kanban-section-head" },
      +          `${tx(i18n, "comments", "Comments")} (${comments.length})`),
               comments.length === 0
      -          ? h("div", { className: "text-xs text-muted-foreground" }, "— no comments —")
      +          ? h("div", { className: "text-xs text-muted-foreground" },
      +              tx(i18n, "noComments", "— no comments —"))
                 : comments.map(function (c) {
                     return h("div", { key: c.id, className: "hermes-kanban-comment" },
                       h("div", { className: "hermes-kanban-comment-head" },
      @@ -2187,7 +2291,8 @@
                   }),
             ),
             h("div", { className: "hermes-kanban-section" },
      -        h("div", { className: "hermes-kanban-section-head" }, `Events (${events.length})`),
      +        h("div", { className: "hermes-kanban-section-head" },
      +          `${tx(i18n, "events", "Events")} (${events.length})`),
               events.slice().reverse().slice(0, 20).map(function (e) {
                 const isDiag = isDiagnosticEvent(e.kind);
                 const phantoms = isDiag ? phantomIdsFromEvent(e) : [];
      @@ -2202,7 +2307,7 @@
                     ? h("div", { className: "hermes-kanban-event-header" },
                         h("span", { className: "hermes-kanban-event-warning-icon" }, "⚠"),
                         h("span", { className: "hermes-kanban-event-warning-label" },
      -                    DIAGNOSTIC_EVENT_LABELS[e.kind] || e.kind),
      +                    getDiagnosticEventLabel(i18n, e.kind) || e.kind),
                         h("span", { className: "hermes-kanban-event-ago" },
                           timeAgo ? timeAgo(e.created_at) : ""),
                       )
      @@ -2214,7 +2319,7 @@
                   isDiag && phantoms.length > 0
                     ? h("div", { className: "hermes-kanban-event-phantom-row" },
                         h("span", { className: "hermes-kanban-event-phantom-label" },
      -                    "Phantom ids:"),
      +                    tx(i18n, "phantomIds", "Phantom ids:")),
                         phantoms.map(function (pid) {
                           return h("code", {
                             key: pid,
      @@ -2239,6 +2344,7 @@
         // active run if any. Each row shows profile / outcome / elapsed /
         // summary. Collapsed by default when there are more than three runs.
         function RunHistorySection(props) {
      +    const { t } = useI18n();
           const runs = props.runs || [];
           const [expanded, setExpanded] = useState(false);
           if (runs.length === 0) return null;
      @@ -2257,13 +2363,13 @@
           return h("div", { className: "hermes-kanban-section" },
             h("div", { className: "hermes-kanban-section-head-row" },
               h("span", { className: "hermes-kanban-section-head" },
      -          `Run history (${runs.length})`),
      +          `${tx(t, "runHistory", "Run history")} (${runs.length})`),
               !showAll
                 ? h("button", {
                     type: "button",
                     onClick: function () { setExpanded(true); },
                     className: "hermes-kanban-edit-link",
      -              title: "Show all attempts",
      +              title: tx(t, "showAllAttempts", "Show all attempts"),
                   }, `+${runs.length - 3} earlier`)
                 : null,
             ),
      @@ -2274,9 +2380,9 @@
               return h("div", { key: r.id, className: cn("hermes-kanban-run", outcomeClass) },
                 h("div", { className: "hermes-kanban-run-head" },
                   h("span", { className: "hermes-kanban-run-outcome" },
      -              r.ended_at ? (r.outcome || r.status || "ended") : "active"),
      +              r.ended_at ? (r.outcome || r.status || tx(t, "ended", "ended")) : tx(t, "active", "active")),
                   h("span", { className: "hermes-kanban-run-profile" },
      -              r.profile ? `@${r.profile}` : "(no profile)"),
      +              r.profile ? `@${r.profile}` : tx(t, "noProfile", "(no profile)")),
                   h("span", { className: "hermes-kanban-run-elapsed" }, fmtElapsed(r)),
                   h("span", { className: "hermes-kanban-run-ago" },
                     timeAgo ? timeAgo(r.started_at) : ""),
      @@ -2298,6 +2404,7 @@
       
         // Worker log: loads lazily (one GET on mount), refresh button, tail cap.
         function WorkerLogSection(props) {
      +    const { t } = useI18n();
           const [state, setState] = useState({ loading: false, data: null, err: null });
           const load = useCallback(function () {
             setState({ loading: true, data: null, err: null });
      @@ -2313,12 +2420,14 @@
           const data = state.data;
           let body;
           if (state.loading) {
      -      body = h("div", { className: "text-xs text-muted-foreground" }, "Loading log…");
      +      body = h("div", { className: "text-xs text-muted-foreground" },
      +        tx(t, "loadingLog", "Loading log…"));
           } else if (state.err) {
             body = h("div", { className: "text-xs text-destructive" }, state.err);
           } else if (!data || !data.exists) {
             body = h("div", { className: "text-xs text-muted-foreground italic" },
      -        "— no worker log yet (task hasn't spawned or log was rotated away) —");
      +        tx(t, "noWorkerLog",
      +          "— no worker log yet (task hasn't spawned or log was rotated away) —"));
           } else {
             body = h("pre", { className: "hermes-kanban-pre hermes-kanban-log" },
               data.content || "(empty)");
      @@ -2327,7 +2436,7 @@
           return h("div", { className: "hermes-kanban-section" },
             h("div", { className: "hermes-kanban-section-head-row" },
               h("span", { className: "hermes-kanban-section-head" },
      -          "Worker log" + (data && data.size_bytes ? ` (${data.size_bytes} B)` : "")),
      +          tx(t, "workerLog", "Worker log") + (data && data.size_bytes ? ` (${data.size_bytes} B)` : "")),
               h("button", {
                 type: "button",
                 onClick: load,
      @@ -2338,7 +2447,9 @@
             body,
             data && data.truncated
               ? h("div", { className: "text-xs text-muted-foreground" },
      -            "(showing last 100 KB — full log at ", data.path, ")")
      +            tx(t, "logTruncated", "(showing last 100 KB — full log at "),
      +            data.path,
      +            tx(t, "logAt", ")"))
               : null,
           );
         }
      @@ -2351,11 +2462,12 @@
         }
       
         function TitleEditor(props) {
      +    const { t } = useI18n();
           const [v, setV] = useState(props.initial);
           const save = function () {
      -      const t = v.trim();
      -      if (!t) return;
      -      props.onSave(t);
      +      const trimmed = v.trim();
      +      if (!trimmed) return;
      +      props.onSave(trimmed);
           };
           return h("div", { className: "hermes-kanban-edit-row" },
             h(Input, {
      @@ -2369,32 +2481,33 @@
             }),
             h(Button, { onClick: save,
               size: "sm",
      -      }, "Save"),
      +      }, tx(t, "save", "Save")),
             h(Button, { onClick: props.onCancel,
               size: "sm",
      -      }, "Cancel"),
      +      }, tx(t, "cancel", "Cancel")),
           );
         }
       
         function AssigneeEditor(props) {
      +    const { t } = useI18n();
           const [editing, setEditing] = useState(false);
           const [v, setV] = useState(props.task.assignee || "");
           useEffect(function () { setV(props.task.assignee || ""); }, [props.task.assignee]);
           if (!editing) {
             return h("div", { className: "hermes-kanban-meta-row" },
      -        h("span", { className: "hermes-kanban-meta-label" }, "Assignee"),
      +        h("span", { className: "hermes-kanban-meta-label" }, tx(t, "assignee", "Assignee")),
               h("span", {
                 className: "hermes-kanban-meta-value hermes-kanban-editable",
                 onClick: function () { setEditing(true); },
      -          title: "Click to edit",
      -        }, props.task.assignee || "unassigned"),
      +          title: tx(t, "clickToEditAssignee", "Click to edit assignee"),
      +        }, props.task.assignee || tx(t, "unassigned", "unassigned")),
             );
           }
           const save = function () {
             props.onPatch({ assignee: v.trim() || "" }).then(function () { setEditing(false); });
           };
           return h("div", { className: "hermes-kanban-meta-row" },
      -      h("span", { className: "hermes-kanban-meta-label" }, "Assignee"),
      +      h("span", { className: "hermes-kanban-meta-label" }, tx(t, "assignee", "Assignee")),
             h(Input, {
               value: v, autoFocus: true,
               onChange: function (e) { setV(e.target.value); },
      @@ -2402,23 +2515,24 @@
                 if (e.key === "Enter") { e.preventDefault(); save(); }
                 if (e.key === "Escape") setEditing(false);
               },
      -        placeholder: "(empty = unassign)",
      +        placeholder: tx(t, "emptyAssignee", "(empty = unassign)"),
               className: "h-7 text-xs flex-1",
             }),
           );
         }
       
         function PriorityEditor(props) {
      +    const { t } = useI18n();
           const [editing, setEditing] = useState(false);
           const [v, setV] = useState(String(props.task.priority || 0));
           useEffect(function () { setV(String(props.task.priority || 0)); }, [props.task.priority]);
           if (!editing) {
             return h("div", { className: "hermes-kanban-meta-row" },
      -        h("span", { className: "hermes-kanban-meta-label" }, "Priority"),
      +        h("span", { className: "hermes-kanban-meta-label" }, tx(t, "priority", "Priority")),
               h("span", {
                 className: "hermes-kanban-meta-value hermes-kanban-editable",
                 onClick: function () { setEditing(true); },
      -          title: "Click to edit",
      +          title: tx(t, "clickToEdit", "Click to edit"),
               }, String(props.task.priority)),
             );
           }
      @@ -2426,7 +2540,7 @@
             props.onPatch({ priority: Number(v) || 0 }).then(function () { setEditing(false); });
           };
           return h("div", { className: "hermes-kanban-meta-row" },
      -      h("span", { className: "hermes-kanban-meta-label" }, "Priority"),
      +      h("span", { className: "hermes-kanban-meta-label" }, tx(t, "priority", "Priority")),
             h(Input, {
               type: "number", value: v, autoFocus: true,
               onChange: function (e) { setV(e.target.value); },
      @@ -2440,6 +2554,7 @@
         }
       
         function BodyEditor(props) {
      +    const { t } = useI18n();
           const [editing, setEditing] = useState(false);
           const [v, setV] = useState(props.task.body || "");
           useEffect(function () { setV(props.task.body || ""); }, [props.task.body]);
      @@ -2448,22 +2563,22 @@
           };
           return h("div", { className: "hermes-kanban-section" },
             h("div", { className: "hermes-kanban-section-head-row" },
      -        h("span", { className: "hermes-kanban-section-head" }, "Description"),
      +        h("span", { className: "hermes-kanban-section-head" }, tx(t, "description", "Description")),
               editing
                 ? h("div", { className: "flex gap-1" },
                     h(Button, { onClick: save,
                       size: "sm",
      -              }, "Save"),
      +              }, tx(t, "save", "Save")),
                     h(Button, { onClick: function () { setEditing(false); setV(props.task.body || ""); },
                       size: "sm",
      -              }, "Cancel"),
      +              }, tx(t, "cancel", "Cancel")),
                   )
                 : h("button", {
                     type: "button",
                     onClick: function () { setEditing(true); },
                     className: "hermes-kanban-edit-link",
                     title: "Edit description",
      -            }, "edit"),
      +            }, tx(t, "edit", "edit")),
             ),
             editing
               ? h("textarea", {
      @@ -2474,30 +2589,32 @@
                 })
               : props.task.body
                 ? h(MarkdownBlock, { source: props.task.body, enabled: props.renderMarkdown })
      -          : h("div", { className: "text-xs text-muted-foreground italic" }, "— no description —"),
      +          : h("div", { className: "text-xs text-muted-foreground italic" },
      +              tx(t, "noDescription", "— no description —")),
           );
         }
       
         function DependencyEditor(props) {
      +    const { t } = useI18n();
           const { task, links, allTasks } = props;
           const [newParent, setNewParent] = useState("");
           const [newChild, setNewChild] = useState("");
           // Filter out self + existing links when offering the "add" dropdown.
           const candidatesFor = function (excludeSet) {
      -      return (allTasks || []).filter(function (t) {
      -        return t.id !== task.id && !excludeSet.has(t.id);
      +      return (allTasks || []).filter(function (tk) {
      +        return tk.id !== task.id && !excludeSet.has(tk.id);
             });
           };
           const parentExclude = new Set([task.id, ...(links.parents || [])]);
           const childExclude  = new Set([task.id, ...(links.children || [])]);
       
           return h("div", { className: "hermes-kanban-section" },
      -      h("div", { className: "hermes-kanban-section-head" }, "Dependencies"),
      +      h("div", { className: "hermes-kanban-section-head" }, tx(t, "dependencies", "Dependencies")),
             h("div", { className: "hermes-kanban-deps-row" },
      -        h("span", { className: "hermes-kanban-deps-label" }, "Parents:"),
      +        h("span", { className: "hermes-kanban-deps-label" }, tx(t, "parents", "Parents:")),
               h("div", { className: "hermes-kanban-deps-chips" },
                 (links.parents || []).length === 0
      -            ? h("span", { className: "hermes-kanban-deps-empty" }, "none")
      +            ? h("span", { className: "hermes-kanban-deps-empty" }, tx(t, "none", "none"))
                   : (links.parents || []).map(function (id) {
                       return h("span", { key: id, className: "hermes-kanban-dep-chip" },
                         id,
      @@ -2505,7 +2622,7 @@
                           type: "button",
                           className: "hermes-kanban-dep-chip-x",
                           onClick: function () { props.onRemoveParent(id); },
      -                    title: "Remove dependency",
      +                    title: tx(t, "removeDependency", "Remove dependency"),
                         }, "×"),
                       );
                     }),
      @@ -2516,10 +2633,10 @@
                 value: newParent,
                 className: "h-7 text-xs flex-1",
               }, selectChangeHandler(setNewParent)),
      -          h(SelectOption, { value: "" }, "— add parent —"),
      -          candidatesFor(parentExclude).map(function (t) {
      -            return h(SelectOption, { key: t.id, value: t.id },
      -              `${t.id} — ${(t.title || "").slice(0, 50)}`);
      +          h(SelectOption, { value: "" }, tx(t, "addParent", "— add parent —")),
      +          candidatesFor(parentExclude).map(function (tk) {
      +            return h(SelectOption, { key: tk.id, value: tk.id },
      +              `${tk.id} — ${(tk.title || "").slice(0, 50)}`);
                 }),
               ),
               h(Button, {
      @@ -2532,10 +2649,10 @@
               }, "+ parent"),
             ),
             h("div", { className: "hermes-kanban-deps-row" },
      -        h("span", { className: "hermes-kanban-deps-label" }, "Children:"),
      +        h("span", { className: "hermes-kanban-deps-label" }, tx(t, "children", "Children:")),
               h("div", { className: "hermes-kanban-deps-chips" },
                 (links.children || []).length === 0
      -            ? h("span", { className: "hermes-kanban-deps-empty" }, "none")
      +            ? h("span", { className: "hermes-kanban-deps-empty" }, tx(t, "none", "none"))
                   : (links.children || []).map(function (id) {
                       return h("span", { key: id, className: "hermes-kanban-dep-chip" },
                         id,
      @@ -2543,7 +2660,7 @@
                           type: "button",
                           className: "hermes-kanban-dep-chip-x",
                           onClick: function () { props.onRemoveChild(id); },
      -                    title: "Remove dependency",
      +                    title: tx(t, "removeDependency", "Remove dependency"),
                         }, "×"),
                       );
                     }),
      @@ -2554,10 +2671,10 @@
                 value: newChild,
                 className: "h-7 text-xs flex-1",
               }, selectChangeHandler(setNewChild)),
      -          h(SelectOption, { value: "" }, "— add child —"),
      -          candidatesFor(childExclude).map(function (t) {
      -            return h(SelectOption, { key: t.id, value: t.id },
      -              `${t.id} — ${(t.title || "").slice(0, 50)}`);
      +          h(SelectOption, { value: "" }, tx(t, "addChild", "— add child —")),
      +          candidatesFor(childExclude).map(function (tk) {
      +            return h(SelectOption, { key: tk.id, value: tk.id },
      +              `${tk.id} — ${(tk.title || "").slice(0, 50)}`);
                 }),
               ),
               h(Button, {
      @@ -2573,7 +2690,8 @@
         }
       
         function StatusActions(props) {
      -    const t = props.task;
      +    const { t } = useI18n();
      +    const task = props.task;
           const [specifyBusy, setSpecifyBusy] = useState(false);
           const [specifyMsg, setSpecifyMsg] = useState(null);
           const b = function (label, patch, enabled, confirmMsg) {
      @@ -2588,7 +2706,7 @@
           // one column where an auxiliary LLM pass is meaningful. Elsewhere
           // the backend would return ok:false with "not in triage" anyway,
           // so hiding the button keeps the action row uncluttered.
      -    const specifyButton = (t.status === "triage" && props.onSpecify)
      +    const specifyButton = (task.status === "triage" && props.onSpecify)
             ? h(Button, {
                 onClick: function () {
                   if (specifyBusy) return;
      @@ -2623,21 +2741,21 @@
           return h("div", null,
             h("div", { className: "hermes-kanban-actions" },
               specifyButton,
      -        b("→ triage",  { status: "triage" },   t.status !== "triage"),
      -        b("→ ready",   { status: "ready" },    t.status !== "ready"),
      +        b("→ triage",  { status: "triage" },   task.status !== "triage"),
      +        b("→ ready",   { status: "ready" },    task.status !== "ready"),
               // No direct → running button: /tasks/:id PATCH rejects status=running
               // with 400 (issue #19535). Tasks enter running only through the
               // dispatcher's claim_task path, which atomically creates the run row,
               // claim lock, and worker process metadata.
      -        b("Block",     { status: "blocked" },
      -          t.status === "running" || t.status === "ready",
      -          DESTRUCTIVE_TRANSITIONS.blocked),
      -        b("Unblock",   { status: "ready" },    t.status === "blocked"),
      -        b("Complete",  { status: "done" },
      -          t.status === "running" || t.status === "ready" || t.status === "blocked",
      -          DESTRUCTIVE_TRANSITIONS.done),
      -        b("Archive",   { status: "archived" }, t.status !== "archived",
      -          DESTRUCTIVE_TRANSITIONS.archived),
      +        b(tx(t, "block", "Block"),     { status: "blocked" },
      +          task.status === "running" || task.status === "ready",
      +          getDestructiveConfirm(t, "blocked")),
      +        b(tx(t, "unblock", "Unblock"),   { status: "ready" },    task.status === "blocked"),
      +        b(tx(t, "complete", "Complete"),  { status: "done" },
      +          task.status === "running" || task.status === "ready" || task.status === "blocked",
      +          getDestructiveConfirm(t, "done")),
      +        b(tx(t, "archive", "Archive"),   { status: "archived" }, task.status !== "archived",
      +          getDestructiveConfirm(t, "archived")),
             ),
             specifyMsg ? h("div", {
               className: specifyMsg.ok
      @@ -2654,19 +2772,21 @@
         // renders when no platforms have a home configured — this section stays
         // invisible for users who haven't set one up.
         function HomeSubsSection(props) {
      +    const { t } = useI18n();
           const channels = props.homeChannels || [];
           if (channels.length === 0) return null;
           const busy = props.homeBusy || {};
           return h("div", { className: "hermes-kanban-section" },
             h("div", { className: "hermes-kanban-section-head" },
      -        "Notify home channels"),
      +        tx(t, "notifyHomeChannels", "Notify home channels")),
             h("div", { className: "hermes-kanban-home-subs" },
               channels.map(function (hc) {
                 const isBusy = !!busy[hc.platform];
                 const label = hc.subscribed ? "✓ " + hc.platform : hc.platform;
      +          const target = `${hc.name} (${hc.chat_id}${hc.thread_id ? " / " + hc.thread_id : ""})`;
                 const title = hc.subscribed
      -            ? `Sending updates to ${hc.name} (${hc.chat_id}${hc.thread_id ? " / " + hc.thread_id : ""}). Click to stop.`
      -            : `Send completed / blocked / gave_up notifications to ${hc.name} (${hc.chat_id}${hc.thread_id ? " / " + hc.thread_id : ""}).`;
      +            ? `${tx(t, "sendingUpdates", "Sending updates to")} ${target}. Click to stop.`
      +            : `${tx(t, "sendNotifications", "Send completed / blocked / gave_up notifications to")} ${target}.`;
                 return h(Button, {
                   key: hc.platform,
                   size: "sm",
    • tests/agent/test_i18n.py
      @@ -156,7 +156,12 @@ def test_t_missing_key_in_non_english_falls_back_to_english(tmp_path, monkeypatc
           (fake_locales / "zh.yaml").write_text("# intentionally empty\n", encoding="utf-8")
           monkeypatch.setattr(i18n, "_locales_dir", lambda: fake_locales)
           i18n.reset_language_cache()
      -    assert i18n.t("foo", lang="zh") == "English Foo"
      +    try:
      +        assert i18n.t("foo", lang="zh") == "English Foo"
      +    finally:
      +        # Clear the cache on teardown so subsequent tests don't see the
      +        # fake "foo: English Foo" catalog instead of the real locales/*.yaml.
      +        i18n.reset_language_cache()
       
       
       def test_t_unknown_language_uses_english():
    • web/src/components/LanguageSwitcher.tsx
      @@ -1,36 +1,100 @@
      +import { useState, useRef, useEffect } from "react";
       import { Button } from "@nous-research/ui/ui/components/button";
       import { Typography } from "@/components/NouiTypography";
       import { useI18n } from "@/i18n/context";
      +import { LOCALE_META } from "@/i18n";
      +import type { Locale } from "@/i18n";
       
       /**
      - * Compact language toggle — shows a clickable flag that switches between
      - * English and Chinese.  Persists choice to localStorage.
      + * Language picker — shows the current language's flag + endonym, opens a
      + * dropdown of all supported locales when clicked.  Persists choice to
      + * localStorage via the I18n context.
      + *
      + * Replaces the older two-state EN↔ZH toggle now that we ship 16 locales
      + * (en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, hu).
        */
       export function LanguageSwitcher() {
         const { locale, setLocale, t } = useI18n();
      +  const [open, setOpen] = useState(false);
      +  const containerRef = useRef<HTMLDivElement>(null);
       
      -  const toggle = () => setLocale(locale === "en" ? "zh" : "en");
      +  // Close on outside click / Escape so the dropdown doesn't trap the user.
      +  useEffect(() => {
      +    if (!open) return;
      +
      +    function onPointerDown(e: PointerEvent) {
      +      if (!containerRef.current) return;
      +      if (!containerRef.current.contains(e.target as Node)) {
      +        setOpen(false);
      +      }
      +    }
      +    function onKey(e: KeyboardEvent) {
      +      if (e.key === "Escape") setOpen(false);
      +    }
      +
      +    document.addEventListener("pointerdown", onPointerDown);
      +    document.addEventListener("keydown", onKey);
      +    return () => {
      +      document.removeEventListener("pointerdown", onPointerDown);
      +      document.removeEventListener("keydown", onKey);
      +    };
      +  }, [open]);
      +
      +  const current = LOCALE_META[locale];
      +  const allLocales = Object.entries(LOCALE_META) as Array<[Locale, typeof current]>;
       
         return (
      -    <Button
      -      ghost
      -      onClick={toggle}
      -      title={t.language.switchTo}
      -      aria-label={t.language.switchTo}
      -      className="px-2 py-1 normal-case tracking-normal font-normal text-xs text-muted-foreground hover:text-foreground"
      -    >
      -      <span className="inline-flex items-center gap-1.5">
      -        <span className="text-base leading-none">
      -          {locale === "en" ? "🇬🇧" : "🇨🇳"}
      +    <div ref={containerRef} className="relative inline-flex">
      +      <Button
      +        ghost
      +        onClick={() => setOpen((v) => !v)}
      +        title={t.language.switchTo}
      +        aria-label={t.language.switchTo}
      +        aria-haspopup="listbox"
      +        aria-expanded={open}
      +        className="px-2 py-1 normal-case tracking-normal font-normal text-xs text-muted-foreground hover:text-foreground"
      +      >
      +        <span className="inline-flex items-center gap-1.5">
      +          <span className="text-base leading-none">{current.flag}</span>
      +          <Typography
      +            mondwest
      +            className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
      +          >
      +            {locale === "en" ? "EN" : current.name}
      +          </Typography>
               </span>
      +      </Button>
       
      -        <Typography
      -          mondwest
      -          className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
      +      {open && (
      +        <div
      +          role="listbox"
      +          aria-label={t.language.switchTo}
      +          className="absolute right-0 top-full mt-1 z-50 min-w-[10rem] rounded-md border border-border bg-popover shadow-md py-1 max-h-80 overflow-y-auto"
               >
      -          {locale === "en" ? "EN" : "中文"}
      -        </Typography>
      -      </span>
      -    </Button>
      +          {allLocales.map(([code, meta]) => {
      +            const selected = code === locale;
      +            return (
      +              <button
      +                key={code}
      +                role="option"
      +                aria-selected={selected}
      +                onClick={() => {
      +                  setLocale(code);
      +                  setOpen(false);
      +                }}
      +                className={
      +                  "w-full text-left px-3 py-1.5 text-xs flex items-center gap-2 hover:bg-accent hover:text-accent-foreground transition-colors " +
      +                  (selected ? "font-semibold text-foreground" : "text-muted-foreground")
      +                }
      +              >
      +                <span className="text-base leading-none">{meta.flag}</span>
      +                <span className="truncate">{meta.name}</span>
      +                {selected && <span className="ml-auto text-xs">✓</span>}
      +              </button>
      +            );
      +          })}
      +        </div>
      +      )}
      +    </div>
         );
       }
    • web/src/i18n/af.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const af: Translations = {
      +  common: {
      +    save: "Stoor",
      +    saving: "Besig om te stoor...",
      +    cancel: "Kanselleer",
      +    close: "Maak toe",
      +    confirm: "Bevestig",
      +    delete: "Skrap",
      +    refresh: "Herlaai",
      +    retry: "Probeer weer",
      +    search: "Soek...",
      +    loading: "Besig om te laai...",
      +    create: "Skep",
      +    creating: "Besig om te skep...",
      +    set: "Stel",
      +    replace: "Vervang",
      +    clear: "Vee uit",
      +    live: "Lewendig",
      +    off: "Af",
      +    enabled: "geaktiveer",
      +    disabled: "gedeaktiveer",
      +    active: "aktief",
      +    inactive: "onaktief",
      +    unknown: "onbekend",
      +    untitled: "Sonder titel",
      +    none: "Geen",
      +    form: "Vorm",
      +    noResults: "Geen resultate",
      +    of: "van",
      +    page: "Bladsy",
      +    msgs: "boodskappe",
      +    tools: "gereedskap",
      +    match: "passing",
      +    other: "Ander",
      +    configured: "gekonfigureer",
      +    removed: "verwyder",
      +    failedToToggle: "Kon nie wissel nie",
      +    failedToRemove: "Kon nie verwyder nie",
      +    failedToReveal: "Kon nie openbaar nie",
      +    collapse: "Vou in",
      +    expand: "Vou uit",
      +    general: "Algemeen",
      +    messaging: "Boodskappe",
      +    pluginLoadFailed:
      +      "Kon nie hierdie inprop se skrip laai nie. Kontroleer die Netwerk-oortjie (dashboard-plugins/…) en die bediener se inprop-pad.",
      +    pluginNotRegistered:
      +      "Die inprop se skrip het nie register() geroep nie, of die skrip het 'n fout gegee. Maak die blaaier-konsole oop vir besonderhede.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Maak navigasie toe",
      +    closeModelTools: "Maak model en gereedskap toe",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Aktiewe Sessies:",
      +    gatewayStatusLabel: "Gateway-status:",
      +    gatewayStrip: {
      +      failed: "Begin het misluk",
      +      off: "Af",
      +      running: "Loop",
      +      starting: "Begin",
      +      stopped: "Gestop",
      +    },
      +    nav: {
      +      analytics: "Analise",
      +      chat: "Klets",
      +      config: "Konfigurasie",
      +      cron: "Cron",
      +      documentation: "Dokumentasie",
      +      keys: "Sleutels",
      +      logs: "Logs",
      +      models: "Modelle",
      +      profiles: "profiele : multi-agente",
      +      plugins: "Inproppe",
      +      sessions: "Sessies",
      +      skills: "Vaardighede",
      +    },
      +    modelToolsSheetSubtitle: "& gereedskap",
      +    modelToolsSheetTitle: "Model",
      +    navigation: "Navigasie",
      +    openDocumentation: "Maak dokumentasie in 'n nuwe oortjie oop",
      +    openNavigation: "Maak navigasie oop",
      +    pluginNavSection: "Inproppe",
      +    sessionsActiveCount: "{count} aktief",
      +    statusOverview: "Statusoorsig",
      +    system: "Stelsel",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Aksie het misluk",
      +    actionFinished: "Voltooi",
      +    actions: "Aksies",
      +    agent: "Agent",
      +    activeSessions: "Aktiewe Sessies",
      +    connected: "Gekoppel",
      +    connectedPlatforms: "Gekoppelde Platforms",
      +    disconnected: "Ontkoppel",
      +    error: "Fout",
      +    failed: "Misluk",
      +    gateway: "Gateway",
      +    gatewayFailedToStart: "Gateway kon nie begin nie",
      +    lastUpdate: "Laaste opdatering",
      +    noneRunning: "Geen",
      +    notRunning: "Loop nie",
      +    pid: "PID",
      +    platformDisconnected: "ontkoppel",
      +    platformError: "fout",
      +    recentSessions: "Onlangse Sessies",
      +    restartGateway: "Herbegin Gateway",
      +    restartingGateway: "Besig om gateway te herbegin…",
      +    running: "Loop",
      +    runningRemote: "Loop (afgeleë)",
      +    startFailed: "Begin het misluk",
      +    starting: "Begin",
      +    startedInBackground: "Begin in agtergrond — kyk logs vir vordering",
      +    stopped: "Gestop",
      +    updateHermes: "Werk Hermes op",
      +    updatingHermes: "Besig om Hermes op te werk…",
      +    waitingForOutput: "Wag vir uitset…",
      +  },
      +
      +  sessions: {
      +    title: "Sessies",
      +    searchPlaceholder: "Soek boodskap-inhoud...",
      +    noSessions: "Nog geen sessies nie",
      +    noMatch: "Geen sessies stem ooreen met jou soektog nie",
      +    startConversation: "Begin 'n gesprek om dit hier te sien",
      +    noMessages: "Geen boodskappe",
      +    untitledSession: "Sessie sonder titel",
      +    deleteSession: "Skrap sessie",
      +    confirmDeleteTitle: "Skrap sessie?",
      +    confirmDeleteMessage:
      +      "Dit verwyder die gesprek en al sy boodskappe permanent. Dit kan nie ongedaan gemaak word nie.",
      +    sessionDeleted: "Sessie geskrap",
      +    failedToDelete: "Kon nie sessie skrap nie",
      +    resumeInChat: "Hervat in Klets",
      +    previousPage: "Vorige bladsy",
      +    nextPage: "Volgende bladsy",
      +    roles: {
      +      user: "Gebruiker",
      +      assistant: "Assistent",
      +      system: "Stelsel",
      +      tool: "Gereedskap",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Tydperk:",
      +    totalTokens: "[redacted:secret-assignment] Tokens",
      +    totalSessions: "Totale Sessies",
      +    apiCalls: "API-oproepe",
      +    dailyTokenUsage: "[redacted:secret-assignment] Tokengebruik",
      +    dailyBreakdown: "Daaglikse Uiteensetting",
      +    perModelBreakdown: "Per-Model Uiteensetting",
      +    topSkills: "Top Vaardighede",
      +    skill: "Vaardigheid",
      +    loads: "Agent Gelaai",
      +    edits: "Agent Bestuur",
      +    lastUsed: "Laas Gebruik",
      +    input: "Inset",
      +    output: "Uitset",
      +    total: "Totaal",
      +    noUsageData: "Geen gebruiksdata vir hierdie tydperk nie",
      +    startSession: "Begin 'n sessie om analise hier te sien",
      +    date: "Datum",
      +    model: "Model",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/dag gem.",
      +    acrossModels: "oor {count} modelle",
      +    inOut: "{input} in / {output} uit",
      +  },
      +
      +  models: {
      +    modelsUsed: "Modelle Gebruik",
      +    estimatedCost: "Geskatte Koste",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "sessies",
      +    avgPerSession: "gem./sessie",
      +    apiCalls: "API-oproepe",
      +    toolCalls: "gereedskap-oproepe",
      +    noModelsData: "Geen modelgebruiksdata vir hierdie tydperk nie",
      +    startSession: "Begin 'n sessie om modeldata hier te sien",
      +  },
      +
      +  logs: {
      +    title: "Logs",
      +    autoRefresh: "Outo-herlaai",
      +    file: "Lêer",
      +    level: "Vlak",
      +    component: "Komponent",
      +    lines: "Reëls",
      +    noLogLines: "Geen logreëls gevind nie",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Dit verwyder die taak van die skedule. Dit kan nie ongedaan gemaak word nie.",
      +    confirmDeleteTitle: "Skrap geskeduleerde taak?",
      +    newJob: "Nuwe Cron-taak",
      +    nameOptional: "Naam (opsioneel)",
      +    namePlaceholder: "bv. Daaglikse opsomming",
      +    prompt: "Opdrag",
      +    promptPlaceholder: "Wat moet die agent met elke uitvoering doen?",
      +    schedule: "Skedule (cron-uitdrukking)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Lewer aan",
      +    scheduledJobs: "Geskeduleerde Take",
      +    noJobs: "Geen cron-take gekonfigureer nie. Skep een hierbo.",
      +    last: "Laaste",
      +    next: "Volgende",
      +    pause: "Pouse",
      +    resume: "Hervat",
      +    triggerNow: "Voer nou uit",
      +    delivery: {
      +      local: "Plaaslik",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Nuwe Profiel",
      +    name: "Naam",
      +    namePlaceholder: "bv. coder, writer, ens.",
      +    nameRequired: "Naam word vereis",
      +    nameRule:
      +      "Slegs kleinletters, syfers, _ en -; moet met 'n letter of syfer begin; tot 64 karakters.",
      +    invalidName: "Ongeldige profielnaam",
      +    cloneFromDefault: "Kloon konfigurasie vanaf verstekprofiel",
      +    allProfiles: "Profiele",
      +    noProfiles: "Geen profiele gevind nie.",
      +    defaultBadge: "verstek",
      +    hasEnv: "env",
      +    model: "Model",
      +    skills: "Vaardighede",
      +    rename: "Hernoem",
      +    editSoul: "Wysig SOUL.md",
      +    soulSection: "SOUL.md (persoonlikheid / stelselopdrag)",
      +    soulPlaceholder: "# Hoe hierdie agent moet optree…",
      +    saveSoul: "Stoor SOUL",
      +    soulSaved: "SOUL.md gestoor",
      +    openInTerminal: "Kopieer CLI-opdrag",
      +    commandCopied: "Na knipbord gekopieer",
      +    copyFailed: "Kon nie kopieer nie",
      +    confirmDeleteTitle: "Skrap profiel?",
      +    confirmDeleteMessage:
      +      "Dit skrap profiel '{name}' permanent — konfigurasie, sleutels, geheue, sessies, vaardighede, cron-take. Kan nie ongedaan gemaak word nie.",
      +    created: "Geskep",
      +    deleted: "Geskrap",
      +    renamed: "Hernoem",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Konteks-enjin",
      +    dashboardSlots: "Dashboard-gleuwe",
      +    disableRuntime: "Deaktiveer",
      +    enableAfterInstall: "Aktiveer ná installasie",
      +    enableRuntime: "Aktiveer",
      +    forceReinstall: "Forseer herinstallasie (skrap eers bestaande gids)",
      +    headline:
      +      "Ontdek, installeer, aktiveer en werk Hermes-inproppe op (`hermes plugins` ekwivalent).",
      +    identifierLabel: "Git-URL of owner/repo",
      +    inactive: "onaktief",
      +    installBtn: "Installeer vanaf Git",
      +    installHeading: "Installeer vanaf GitHub / Git-URL",
      +    installHint: "Gebruik owner/repo-kortvorm of 'n volledige https:// of git@ kloon-URL.",
      +    memoryProviderLabel: "Geheueverskaffer",
      +    missingEnvWarn: "Stel hierdie in Sleutels voordat die inprop kan loop:",
      +    noDashboardTab: "Geen dashboard-oortjie",
      +    openTab: "Maak oop",
      +    orphanHeading: "Slegs-dashboard-uitbreidings (geen ooreenstemmende agent plugin.yaml nie)",
      +    pluginListHeading: "Geïnstalleerde inproppe",
      +    providerDefaults: "ingebou / verstek",
      +    providersHeading: "Looptyd-verskafferinproppe",
      +    providersHint:
      +      "Skryf memory.provider (leeg = ingebou) en context.engine na config.yaml. Tree volgende sessie in werking.",
      +    refreshDashboard: "Herskandeer dashboard-uitbreidings",
      +    removeConfirm: "Verwyder hierdie inprop uit ~/.hermes/plugins/?",
      +    removeHint: "Slegs gebruiker-geïnstalleerde inproppe onder ~/.hermes/plugins kan verwyder word.",
      +    rescanHeading: "SPA-inprop-register",
      +    rescanHint: "Herskandeer ná die byvoeg van lêers op skyf sodat die dashboard-sybalk nuwe manifeste optel.",
      +    runtimeHeading: "Gateway-looptyd (YAML-inproppe)",
      +    saveProviders: "Stoor verskaffer-instellings",
      +    savedProviders: "Verskaffer-instellings gestoor.",
      +    sourceBadge: "Bron",
      +    authRequired: "Verifikasie vereis",
      +    authRequiredHint: "Voer hierdie opdrag uit om te verifieer:",
      +    updateGit: "Git pull",
      +    versionBadge: "Weergawe",
      +    showInSidebar: "Wys in sybalk",
      +    hideFromSidebar: "Versteek van sybalk",
      +  },
      +
      +  skills: {
      +    title: "Vaardighede",
      +    searchPlaceholder: "Soek vaardighede en gereedskapstelle...",
      +    enabledOf: "{enabled}/{total} geaktiveer",
      +    all: "Alles",
      +    categories: "Kategorieë",
      +    filters: "Filters",
      +    noSkills: "Geen vaardighede gevind nie. Vaardighede word gelaai uit ~/.hermes/skills/",
      +    noSkillsMatch: "Geen vaardighede stem ooreen met jou soektog of filter nie.",
      +    skillCount: "{count} vaardighe{s}id",
      +    resultCount: "{count} resulta{s}at",
      +    noDescription: "Geen beskrywing beskikbaar nie.",
      +    toolsets: "Gereedskapstelle",
      +    toolsetLabel: "{name} gereedskapstel",
      +    noToolsetsMatch: "Geen gereedskapstelle stem ooreen met die soektog nie.",
      +    setupNeeded: "Opstelling nodig",
      +    disabledForCli: "Gedeaktiveer vir CLI",
      +    more: "+{count} meer",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Filters",
      +    sections: "Afdelings",
      +    exportConfig: "Voer konfigurasie uit as JSON",
      +    importConfig: "Voer konfigurasie in vanaf JSON",
      +    resetDefaults: "Stel terug na verstek",
      +    resetScopeTooltip: "Stel {scope} terug na verstek",
      +    confirmResetScope: "Stel alle {scope}-instellings terug na hul verstek? Dit werk slegs die vorm op — veranderinge word nie na config.yaml geskryf voordat jy Stoor druk nie.",
      +    resetScopeToast: "{scope} teruggestel na verstek — kontroleer en Stoor om te behou",
      +    rawYaml: "Rou YAML-konfigurasie",
      +    searchResults: "Soekresultate",
      +    fields: "veld{s}",
      +    noFieldsMatch: 'Geen velde stem ooreen met "{query}" nie',
      +    configSaved: "Konfigurasie gestoor",
      +    yamlConfigSaved: "YAML-konfigurasie gestoor",
      +    failedToSave: "Kon nie stoor nie",
      +    failedToSaveYaml: "Kon nie YAML stoor nie",
      +    failedToLoadRaw: "Kon nie rou konfigurasie laai nie",
      +    configImported: "Konfigurasie ingevoer — kontroleer en stoor",
      +    invalidJson: "Ongeldige JSON-lêer",
      +    categories: {
      +      general: "Algemeen",
      +      agent: "Agent",
      +      terminal: "Terminaal",
      +      display: "Vertoon",
      +      delegation: "Delegasie",
      +      memory: "Geheue",
      +      compression: "Kompressie",
      +      security: "Sekuriteit",
      +      browser: "Blaaier",
      +      voice: "Stem",
      +      tts: "Teks-na-Spraak",
      +      stt: "Spraak-na-Teks",
      +      logging: "Aantekening",
      +      discord: "Discord",
      +      auxiliary: "Hulpmiddels",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "Veranderinge word onmiddellik na skyf gestoor. Aktiewe sessies tel nuwe sleutels outomaties op.",
      +    confirmClearMessage:
      +      "Die gestoorde waarde vir hierdie veranderlike sal uit jou .env-lêer verwyder word. Dit kan nie vanaf die UI ongedaan gemaak word nie.",
      +    confirmClearTitle: "Vee hierdie sleutel uit?",
      +    description: "Bestuur API-sleutels en geheime gestoor in",
      +    hideAdvanced: "Versteek Gevorderd",
      +    showAdvanced: "Wys Gevorderd",
      +    llmProviders: "LLM-verskaffers",
      +    providersConfigured: "{configured} van {total} verskaffers gekonfigureer",
      +    getKey: "Kry sleutel",
      +    notConfigured: "{count} nie gekonfigureer nie",
      +    notSet: "Nie gestel nie",
      +    keysCount: "{count} sleutel{s}",
      +    enterValue: "Voer waarde in...",
      +    replaceCurrentValue: "Vervang huidige waarde ({preview})",
      +    showValue: "Wys werklike waarde",
      +    hideValue: "Versteek waarde",
      +  },
      +
      +  oauth: {
      +    title: "Verskaffer-aanmeldings (OAuth)",
      +    providerLogins: "Verskaffer-aanmeldings (OAuth)",
      +    description: "{connected} van {total} OAuth-verskaffers gekoppel. Aanmeldvloei loop tans via die CLI; klik Kopieer opdrag en plak in 'n terminaal om op te stel.",
      +    connected: "Gekoppel",
      +    expired: "Verval",
      +    notConnected: "Nie gekoppel nie. Voer {command} uit in 'n terminaal.",
      +    runInTerminal: "in 'n terminaal.",
      +    noProviders: "Geen OAuth-bekwame verskaffers opgespoor nie.",
      +    login: "Meld aan",
      +    disconnect: "Ontkoppel",
      +    managedExternally: "Ekstern bestuur",
      +    copied: "Gekopieer ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Kopieer CLI-opdrag (vir ekstern / terugval)",
      +    connect: "Koppel",
      +    sessionExpires: "Sessie verval oor {time}",
      +    initiatingLogin: "Aanmeldvloei word begin…",
      +    exchangingCode: "Kode word vir tokens omgeruil…",
      +    connectedClosing: "Gekoppel! Besig om toe te maak…",
      +    loginFailed: "Aanmelding het misluk.",
      +    sessionExpired: "Sessie het verval. Klik Probeer weer om 'n nuwe aanmelding te begin.",
      +    reOpenAuth: "Heropen verifikasiebladsy",
      +    reOpenVerification: "Heropen verifikasiebladsy",
      +    submitCode: "Dien kode in",
      +    pasteCode: "Plak magtigingskode (met #state agtervoegsel is in die haak)",
      +    waitingAuth: "Wag vir jou om in die blaaier te magtig…",
      +    enterCodePrompt: "'n Nuwe oortjie het oopgegaan. Voer hierdie kode in indien gevra:",
      +    pkceStep1: "'n Nuwe oortjie het na claude.ai oopgegaan. Meld aan en klik Magtig.",
      +    pkceStep2: "Kopieer die magtigingskode wat ná magtiging vertoon word.",
      +    pkceStep3: "Plak dit hieronder en dien in.",
      +    flowLabels: {
      +      pkce: "Blaaier-aanmelding (PKCE)",
      +      device_code: "Toestel-kode",
      +      external: "Eksterne CLI",
      +    },
      +    expiresIn: "verval oor {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Skakel oor na Engels",
      +  },
      +
      +  theme: {
      +    title: "Tema",
      +    switchTheme: "Wissel tema",
      +  },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Versamelbare Hermes-kentekens wat verdien word uit werklike sessiegeskiedenis. Bekende, onvoltooide prestasies word as Ontdek vertoon; Geheime prestasies bly verborge totdat die eerste ooreenstemmende gedrag verskyn.",
      +      scan_subtitle:
      +        "Hermes-sessiegeskiedenis word geskandeer. Die eerste skandering kan 5–10 sekondes neem op groot geskiedenisse.",
      +    },
      +    actions: {
      +      rescan: "Herskandeer",
      +    },
      +    stats: {
      +      unlocked: "Ontsluit",
      +      unlocked_hint: "verdiende kentekens",
      +      discovered: "Ontdek",
      +      discovered_hint: "bekend, nog nie verdien nie",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] tot eerste sein",
      +      highest_tier: "Hoogste vlak",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Jongste",
      +      latest_hint_empty: "gebruik Hermes meer",
      +      none_yet: "Nog geen",
      +    },
      +    state: {
      +      unlocked: "Ontsluit",
      +      discovered: "Ontdek",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Teiken {tier}",
      +      hidden: "Verborge",
      +      complete: "Voltooi",
      +      objective: "Doelwit",
      +    },
      +    progress: {
      +      hidden: "verborge",
      +    },
      +    scan: {
      +      building_headline: "Prestasieprofiel word gebou…",
      +      building_detail:
      +        "Sessies, gereedskaproepe, modelmetadata en ontsluitstatus word gelees.",
      +      starting_headline: "Prestasieskandering begin…",
      +      progress_detail:
      +        "{scanned} van {total} sessies geskandeer · {pct}%. Kentekens ontsluit soos meer geskiedenis instroom.",
      +      idle_detail:
      +        "Sessies, gereedskaproepe, modelmetadata en ontsluitstatus word gelees. Kentekens verskyn hier soos hulle ontsluit.",
      +    },
      +    guide: {
      +      tiers_header: "Vlakke",
      +      secret_header: "[redacted:secret-assignment] prestasies",
      +      secret_body:
      +        "Geheime hou hul presiese sneller verborge. Sodra Hermes 'n verwante sein sien, word die kaart Ontdek en wys sy vereiste.",
      +      scan_status_header: "Skanderingstatus",
      +      scan_status_body:
      +        "Hermes skandeer plaaslike geskiedenis een keer, daarna verskyn kaarte outomaties. Niks is vasgevang as dit 'n paar sekondes neem nie.",
      +      what_scanned_header: "Wat geskandeer word",
      +      what_scanned_body:
      +        "Sessies, gereedskaproepe, modelmetadata, foute, prestasies en plaaslike ontsluitstatus.",
      +    },
      +    card: {
      +      share_title: "Deel hierdie prestasie",
      +      share_label: "Deel {name}",
      +      share_text: "Deel",
      +      how_to_reveal: "Hoe om te onthul",
      +      what_counts: "Wat tel",
      +      evidence_label: "Bewys",
      +      evidence_session_fallback: "sessie",
      +      no_evidence: "Nog geen bewys nie",
      +    },
      +    latest: {
      +      header: "Onlangse ontsluitings",
      +    },
      +    empty: {
      +      no_secrets_header: "Geen verborge geheime in hierdie skandering oor nie.",
      +      no_secrets_body:
      +        "Wenk: geheime begin gewoonlik by ongewone mislukkings of magsgebruikerspatrone — poortbotsings, toestemmingsmure, ontbrekende env-veranderlikes, YAML-foute, Docker-botsings, terugrol/kontrolepunt-gebruik, kasterugslae of klein regstellings na baie rooi teks.",
      +    },
      +    filters: {
      +      all_categories: "Alles",
      +      visibility_all: "alles",
      +      visibility_unlocked: "ontsluit",
      +      visibility_discovered: "ontdek",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Deel prestasie",
      +      header: "Deel: {name}",
      +      close: "Maak toe",
      +      rendering: "Lewer tans…",
      +      card_alt: "{name} deelkaart",
      +      error_generic: "Iets het verkeerd geloop.",
      +      x_title: "Maak X oop met 'n vooraf-ingevulde plasing",
      +      x_button: "Deel op X",
      +      copy_title: "Kopieer die beeld om in jou plasing te plak",
      +      copy_button: "Kopieer beeld",
      +      copied: "Gekopieer ✓",
      +      download_button: "Laai PNG af",
      +      hint:
      +        "Deel op X maak 'n vooraf-ingevulde plasing in 'n nuwe oortjie oop. Klik eers op Kopieer beeld as jy die 1200×630-kenteken aangeheg wil hê — X laat jou dit direk in die tweet-skrywer plak. Laai PNG af stoor die lêer om enige plek te gebruik.",
      +      clipboard_unsupported:
      +        "Beeldkopiëring na knipbord word nie in hierdie blaaier ondersteun nie — gebruik eerder Aflaai.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Kanban-bord word gelaai…",
      +    loadFailed: "Kon nie Kanban-bord laai nie: ",
      +    loadFailedHint:
      +      "Die agterkant skep kanban.db outomaties met die eerste lees. Indien hierdie probleem aanhou, raadpleeg die paneellogboeke.",
      +    board: "Bord",
      +    newBoard: "+ Nuwe bord",
      +    newBoardTitle: "Nuwe bord",
      +    newBoardDescription:
      +      "Borde laat u toe om onverwante werkstrome te skei — een per projek, repositorium of domein. Werkers op een bord sien nooit 'n ander bord se take nie.",
      +    slug: "Slug",
      +    slugHint: "— kleinletters, koppeltekens, bv. atm10-server",
      +    displayName: "Vertoonnaam",
      +    displayNameHint: "(opsioneel)",
      +    description: "Beskrywing",
      +    descriptionHint: "(opsioneel)",
      +    icon: "Ikoon",
      +    iconHint: "(enkele karakter of emoji)",
      +    switchAfterCreate: "Skakel oor na hierdie bord nadat dit geskep is",
      +    cancel: "Kanselleer",
      +    creating: "Word geskep…",
      +    createBoard: "Skep bord",
      +    search: "Soek",
      +    filterCards: "Filter kaarte…",
      +    tenant: "Huurder",
      +    allTenants: "Alle huurders",
      +    assignee: "Toegewysde",
      +    allProfiles: "Alle profiele",
      +    showArchived: "Wys gearchiveerde",
      +    lanesByProfile: "Bane per profiel",
      +    nudgeDispatcher: "Por versender aan",
      +    refresh: "Verfris",
      +    selected: "gekies",
      +    complete: "Voltooi",
      +    archive: "Argiveer",
      +    apply: "Pas toe",
      +    clear: "Maak skoon",
      +    createTask: "Skep taak in hierdie kolom",
      +    noTasks: "— geen take —",
      +    unassigned: "nie toegewys nie",
      +    untitled: "(sonder titel)",
      +    loadingDetail: "Word gelaai…",
      +    addComment: "Voeg 'n opmerking by… (Enter om in te dien)",
      +    comment: "Opmerking",
      +    status: "Status",
      +    workspace: "Werkruimte",
      +    skills: "Vaardighede",
      +    createdBy: "Geskep deur",
      +    result: "Resultaat",
      +    comments: "Opmerkings",
      +    events: "Gebeurtenisse",
      +    runHistory: "Uitvoergeskiedenis",
      +    workerLog: "Werker-log",
      +    loadingLog: "Log word gelaai…",
      +    noWorkerLog:
      +      "— nog geen werker-log nie (taak is nog nie ontketen nie of die log is geroteer) —",
      +    noDescription: "— geen beskrywing —",
      +    noComments: "— geen opmerkings —",
      +    edit: "redigeer",
      +    save: "Stoor",
      +    dependencies: "Afhanklikhede",
      +    parents: "Ouers:",
      +    children: "Kinders:",
      +    none: "geen",
      +    addParent: "— voeg ouer by —",
      +    addChild: "— voeg kind by —",
      +    removeDependency: "Verwyder afhanklikheid",
      +    block: "Blokkeer",
      +    unblock: "Deblokkeer",
      +    notifyHomeChannels: "Stel tuiskanale in kennis",
      +    diagnostics: "Diagnostiek",
      +    hide: "Versteek",
      +    show: "Wys",
      +    attention: "Aandag",
      +    tasksNeedAttention: "take benodig aandag",
      +    taskNeedsAttention: "1 taak benodig aandag",
      +    diagnostic: "diagnose",
      +    open: "Maak oop",
      +    close: "Sluit (Esc)",
      +    reassignTo: "Hertoeken aan:",
      +    copied: "Gekopieer",
      +    copyCommand: "Kopieer opdrag na knipbord",
      +    reclaim: "Heroor",
      +    reassign: "Hertoeken",
      +    renderingError: "Kanban-oortjie het 'n weergawefout teëgekom",
      +    reloadView: "Herlaai aansig",
      +    wsAuthFailed:
      +      "WebSocket-verifikasie het misluk — herlaai die bladsy om die sessietoken te verfris.",
      +    markDone: "Merk {n} take as klaar?",
      +    markArchived: "Argiveer {n} take?",
      +    warning: "Waarskuwing",
      +    phantomIds: "Spook-ID's:",
      +    active: "aktief",
      +    ended: "geëindig",
      +    noProfile: "(geen profiel)",
      +    showAllAttempts: "Wys alle pogings",
      +    sendingUpdates: "Stuur opdaterings na",
      +    sendNotifications: "Stuur completed / blocked / gave_up kennisgewings na",
      +    archiveBoardConfirm:
      +      "Argiveer bord '{name}'? Dit sal na boards/_archived/ geskuif word sodat u dit later kan herstel. Take op hierdie bord sal nie meer in die UI verskyn nie.",
      +    archiveBoardTitle: "Argiveer hierdie bord",
      +    boardSwitcherHint: "Borde laat u toe om onverwante werkstrome te skei",
      +    taskCreatedWarning: "Taak geskep, maar: ",
      +    moveFailed: "Skuif het misluk: ",
      +    bulkFailed: "Grootmaat: ",
      +    completionBlockedHallucination: "⚠ Voltooiing geblokkeer — spook-kaart-ID's",
      +    suspectedHallucinatedReferences: "⚠ Teks het na spook-kaart-ID's verwys",
      +    pickProfileFirst: "Kies eers 'n profiel.",
      +    unblockedMessage: "{id} gedeblokkeer. Taak is gereed vir die volgende tik.",
      +    unblockFailed: "Deblokkering het misluk: ",
      +    reclaimedMessage: "{id} heroor. Taak is terug op gereed.",
      +    reclaimFailed: "Heroornaming het misluk: ",
      +    reassignedMessage: "{id} hertoegeken aan {profile}.",
      +    reassignFailed: "Hertoekenning het misluk: ",
      +    selectForBulk: "Kies vir grootmaataksies",
      +    clickToEdit: "Klik om te redigeer",
      +    clickToEditAssignee: "Klik om toegewysde te redigeer",
      +    emptyAssignee: "(leeg = ontbind toekenning)",
      +    columnLabels: {
      +      triage: "Triage",
      +      todo: "Te doen",
      +      ready: "Gereed",
      +      running: "Aan die gang",
      +      blocked: "Geblokkeer",
      +      done: "Klaar",
      +      archived: "Gearchiveer",
      +    },
      +    columnHelp: {
      +      triage: "Rou idees — 'n spesifiseerder sal die spesifikasie uitwerk",
      +      todo: "Wag op afhanklikhede of nie toegewys nie",
      +      ready: "Toegewys en wag vir 'n versender-tik",
      +      running: "Deur 'n werker geëis — in vlug",
      +      blocked: "Werker het mensinvoer aangevra",
      +      done: "Voltooi",
      +      archived: "Gearchiveer",
      +    },
      +    confirmDone:
      +      "Merk hierdie taak as klaar? Die werker se eis word vrygestel en afhanklike kinders word gereed.",
      +    confirmArchive:
      +      "Argiveer hierdie taak? Dit verdwyn uit die verstek-bordaansig.",
      +    confirmBlocked:
      +      "Merk hierdie taak as geblokkeer? Die werker se eis word vrygestel.",
      +    completionSummary:
      +      "Voltooiingsopsomming vir {label}. Dit word as die taak se result gestoor.",
      +    completionSummaryRequired:
      +      "'n Voltooiingsopsomming is verpligtend voordat 'n taak as klaar gemerk word.",
      +    triagePlaceholder: "Rowwe idee — KI sal dit spesifiseer…",
      +    taskTitlePlaceholder: "Nuwe taaktitel…",
      +    specifier: "spesifiseerder",
      +    assigneePlaceholder: "toegewysde",
      +    priority: "Prioriteit",
      +    skillsPlaceholder:
      +      "vaardighede (opsioneel, kommageskei): translation, github-code-review",
      +    noParent: "— geen ouer —",
      +    workspacePathDir: "werkruimtepad (verpligtend, bv. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "werkruimtepad (opsioneel, afgelei van toegewysde indien leeg)",
      +    logTruncated: "(toon laaste 100 KB — volledige log by ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/context.tsx
      @@ -2,14 +2,74 @@ import { createContext, useContext, useState, useCallback, type ReactNode } from
       import type { Locale, Translations } from "./types";
       import { en } from "./en";
       import { zh } from "./zh";
      +import { zhHant } from "./zh-hant";
      +import { ja } from "./ja";
      +import { de } from "./de";
      +import { es } from "./es";
      +import { fr } from "./fr";
      +import { tr } from "./tr";
      +import { uk } from "./uk";
      +import { af } from "./af";
      +import { ko } from "./ko";
      +import { it } from "./it";
      +import { ga } from "./ga";
      +import { pt } from "./pt";
      +import { ru } from "./ru";
      +import { hu } from "./hu";
       
      -const TRANSLATIONS: Record<Locale, Translations> = { en, zh };
      +const TRANSLATIONS: Record<Locale, Translations> = {
      +  en,
      +  zh,
      +  "zh-hant": zhHant,
      +  ja,
      +  de,
      +  es,
      +  fr,
      +  tr,
      +  uk,
      +  af,
      +  ko,
      +  it,
      +  ga,
      +  pt,
      +  ru,
      +  hu,
      +};
      +
      +// Display metadata for the language picker — endonym (native name) so users
      +// recognize their language even if they don't speak the current UI language,
      +// plus a flag emoji for visual scanning.  Exposed as a constant so the
      +// LanguageSwitcher and any future settings page can share the same list.
      +export const LOCALE_META: Record<Locale, { name: string; flag: string }> = {
      +  en: { name: "English", flag: "🇬🇧" },
      +  zh: { name: "简体中文", flag: "🇨🇳" },
      +  "zh-hant": { name: "繁體中文", flag: "🇹🇼" },
      +  ja: { name: "日本語", flag: "🇯🇵" },
      +  de: { name: "Deutsch", flag: "🇩🇪" },
      +  es: { name: "Español", flag: "🇪🇸" },
      +  fr: { name: "Français", flag: "🇫🇷" },
      +  tr: { name: "Türkçe", flag: "🇹🇷" },
      +  uk: { name: "Українська", flag: "🇺🇦" },
      +  af: { name: "Afrikaans", flag: "🇿🇦" },
      +  ko: { name: "한국어", flag: "🇰🇷" },
      +  it: { name: "Italiano", flag: "🇮🇹" },
      +  ga: { name: "Gaeilge", flag: "🇮🇪" },
      +  pt: { name: "Português", flag: "🇵🇹" },
      +  ru: { name: "Русский", flag: "🇷🇺" },
      +  hu: { name: "Magyar", flag: "🇭🇺" },
      +};
      +
      +const SUPPORTED_LOCALES = Object.keys(TRANSLATIONS) as Locale[];
       const STORAGE_KEY = "hermes-locale";
       
      +function isLocale(value: string): value is Locale {
      +  return (SUPPORTED_LOCALES as string[]).includes(value);
      +}
      +
       function getInitialLocale(): Locale {
         try {
           const stored = localStorage.getItem(STORAGE_KEY);
      -    if (stored === "en" || stored === "zh") return stored;
      +    if (stored && isLocale(stored)) return stored;
         } catch {
           // SSR or privacy mode
         }
    • web/src/i18n/de.ts
      @@ -0,0 +1,695 @@
      +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: "Erneut versuchen",
      +    search: "Suchen...",
      +    loading: "Lädt...",
      +    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: "Werkzeuge",
      +    match: "Treffer",
      +    other: "Sonstige",
      +    configured: "konfiguriert",
      +    removed: "entfernt",
      +    failedToToggle: "Umschalten fehlgeschlagen",
      +    failedToRemove: "Entfernen fehlgeschlagen",
      +    failedToReveal: "Anzeigen fehlgeschlagen",
      +    collapse: "Einklappen",
      +    expand: "Ausklappen",
      +    general: "Allgemein",
      +    messaging: "Messaging",
      +    pluginLoadFailed:
      +      "Das Skript dieses Plugins konnte nicht geladen werden. Prüfe den Netzwerk-Tab (dashboard-plugins/…) und den Plugin-Pfad des Servers.",
      +    pluginNotRegistered:
      +      "Das Skript des Plugins hat register() nicht aufgerufen oder ist fehlgeschlagen. Öffne 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: "Startet",
      +      stopped: "Gestoppt",
      +    },
      +    nav: {
      +      analytics: "Analyse",
      +      chat: "Chat",
      +      config: "Konfiguration",
      +      cron: "Cron",
      +      documentation: "Dokumentation",
      +      keys: "Schlüssel",
      +      logs: "Protokolle",
      +      models: "Modelle",
      +      profiles: "Profile : Multi-Agenten",
      +      plugins: "Plugins",
      +      sessions: "Sitzungen",
      +      skills: "Skills",
      +    },
      +    modelToolsSheetSubtitle: "& 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: "Letzte Aktualisierung",
      +    noneRunning: "Keine",
      +    notRunning: "Läuft nicht",
      +    pid: "PID",
      +    platformDisconnected: "getrennt",
      +    platformError: "Fehler",
      +    recentSessions: "Letzte Sitzungen",
      +    restartGateway: "Gateway neu starten",
      +    restartingGateway: "Gateway wird neu gestartet…",
      +    running: "Läuft",
      +    runningRemote: "Läuft (remote)",
      +    startFailed: "Start fehlgeschlagen",
      +    starting: "Startet",
      +    startedInBackground: "Im Hintergrund gestartet — siehe Protokolle für den Fortschritt",
      +    stopped: "Gestoppt",
      +    updateHermes: "Hermes aktualisieren",
      +    updatingHermes: "Hermes wird aktualisiert…",
      +    waitingForOutput: "Warte auf Ausgabe…",
      +  },
      +
      +  sessions: {
      +    title: "Sitzungen",
      +    searchPlaceholder: "Nachrichteninhalt suchen...",
      +    noSessions: "Noch keine Sitzungen",
      +    noMatch: "Keine Sitzungen entsprechen deiner Suche",
      +    startConversation: "Starte eine Unterhaltung, um sie hier zu sehen",
      +    noMessages: "Keine Nachrichten",
      +    untitledSession: "Sitzung ohne Titel",
      +    deleteSession: "Sitzung löschen",
      +    confirmDeleteTitle: "Sitzung löschen?",
      +    confirmDeleteMessage:
      +      "Dies entfernt 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: "[redacted:secret-assignment] gesamt",
      +    totalSessions: "Sitzungen gesamt",
      +    apiCalls: "API-Aufrufe",
      +    dailyTokenUsage: "[redacted:secret-assignment] Token-Nutzung",
      +    dailyBreakdown: "Tagesaufschlüsselung",
      +    perModelBreakdown: "Aufschlüsselung pro Modell",
      +    topSkills: "Top-Skills",
      +    skill: "Skill",
      +    loads: "Agent geladen",
      +    edits: "Agent verwaltet",
      +    lastUsed: "Zuletzt verwendet",
      +    input: "Eingabe",
      +    output: "Ausgabe",
      +    total: "Gesamt",
      +    noUsageData: "Keine Nutzungsdaten für diesen Zeitraum",
      +    startSession: "Starte eine Sitzung, um hier Analysen zu sehen",
      +    date: "Datum",
      +    model: "Modell",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/Tag Ø",
      +    acrossModels: "über {count} Modelle",
      +    inOut: "{input} ein / {output} aus",
      +  },
      +
      +  models: {
      +    modelsUsed: "Verwendete Modelle",
      +    estimatedCost: "Gesch. Kosten",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "Sitzungen",
      +    avgPerSession: "Ø/Sitzung",
      +    apiCalls: "API-Aufrufe",
      +    toolCalls: "Werkzeug-Aufrufe",
      +    noModelsData: "Keine Modellnutzungsdaten für diesen Zeitraum",
      +    startSession: "Starte eine Sitzung, um hier Modelldaten zu sehen",
      +  },
      +
      +  logs: {
      +    title: "Protokolle",
      +    autoRefresh: "Auto-Aktualisierung",
      +    file: "Datei",
      +    level: "Stufe",
      +    component: "Komponente",
      +    lines: "Zeilen",
      +    noLogLines: "Keine Protokollzeilen gefunden",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Damit wird die Aufgabe aus dem Zeitplan entfernt. Dies kann nicht rückgängig gemacht werden.",
      +    confirmDeleteTitle: "Geplante Aufgabe löschen?",
      +    newJob: "Neue Cron-Aufgabe",
      +    nameOptional: "Name (optional)",
      +    namePlaceholder: "z. B. Tägliche Zusammenfassung",
      +    prompt: "Prompt",
      +    promptPlaceholder: "Was soll der Agent bei jedem Lauf tun?",
      +    schedule: "Zeitplan (Cron-Ausdruck)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Zustellen an",
      +    scheduledJobs: "Geplante Aufgaben",
      +    noJobs: "Keine Cron-Aufgaben konfiguriert. Erstelle oben eine.",
      +    last: "Zuletzt",
      +    next: "Nächste",
      +    pause: "Pausieren",
      +    resume: "Fortsetzen",
      +    triggerNow: "Jetzt auslösen",
      +    delivery: {
      +      local: "Lokal",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Neues Profil",
      +    name: "Name",
      +    namePlaceholder: "z. B. coder, writer usw.",
      +    nameRequired: "Name ist erforderlich",
      +    nameRule:
      +      "Nur Kleinbuchstaben, Ziffern, _ und -; muss mit einem Buchstaben oder einer Ziffer beginnen; maximal 64 Zeichen.",
      +    invalidName: "Ungültiger Profilname",
      +    cloneFromDefault: "Konfiguration vom Standardprofil klonen",
      +    allProfiles: "Profile",
      +    noProfiles: "Keine Profile gefunden.",
      +    defaultBadge: "Standard",
      +    hasEnv: "env",
      +    model: "Modell",
      +    skills: "Skills",
      +    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 Zwischenablage kopiert",
      +    copyFailed: "Kopieren fehlgeschlagen",
      +    confirmDeleteTitle: "Profil löschen?",
      +    confirmDeleteMessage:
      +      "Damit wird das Profil '{name}' dauerhaft gelöscht — Konfiguration, Schlüssel, Erinnerungen, Sitzungen, Skills, 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 (bestehenden Ordner zuerst löschen)",
      +    headline:
      +      "Hermes-Plugins entdecken, installieren, aktivieren und aktualisieren (entspricht `hermes plugins`).",
      +    identifierLabel: "Git-URL oder owner/repo",
      +    inactive: "inaktiv",
      +    installBtn: "Aus Git installieren",
      +    installHeading: "Aus GitHub / Git-URL installieren",
      +    installHint: "Verwende owner/repo-Kurzform oder eine vollständige https:// oder git@ Klon-URL.",
      +    memoryProviderLabel: "Speicheranbieter",
      +    missingEnvWarn: "Setze diese unter Schlüssel, bevor das Plugin laufen kann:",
      +    noDashboardTab: "Kein Dashboard-Tab",
      +    openTab: "Öffnen",
      +    orphanHeading: "Nur-Dashboard-Erweiterungen (keine Übereinstimmung mit Agent plugin.yaml)",
      +    pluginListHeading: "Installierte Plugins",
      +    providerDefaults: "eingebaut / Standard",
      +    providersHeading: "Laufzeit-Anbieter-Plugins",
      +    providersHint:
      +      "Schreibt memory.provider (leer = eingebaut) und context.engine in config.yaml. Wirkt sich auf die nächste 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-Registry",
      +    rescanHint: "Nach dem Hinzufügen von Dateien auf dem Datenträger erneut scannen, damit die Sidebar neue Manifeste erkennt.",
      +    runtimeHeading: "Gateway-Laufzeit (YAML-Plugins)",
      +    saveProviders: "Anbieter-Einstellungen speichern",
      +    savedProviders: "Anbieter-Einstellungen gespeichert.",
      +    sourceBadge: "Quelle",
      +    authRequired: "Authentifizierung erforderlich",
      +    authRequiredHint: "Führe diesen Befehl aus, um dich zu authentifizieren:",
      +    updateGit: "Git pull",
      +    versionBadge: "Version",
      +    showInSidebar: "In Sidebar anzeigen",
      +    hideFromSidebar: "Aus Sidebar ausblenden",
      +  },
      +
      +  skills: {
      +    title: "Skills",
      +    searchPlaceholder: "Skills und Toolsets suchen...",
      +    enabledOf: "{enabled}/{total} aktiviert",
      +    all: "Alle",
      +    categories: "Kategorien",
      +    filters: "Filter",
      +    noSkills: "Keine Skills gefunden. Skills werden aus ~/.hermes/skills/ geladen",
      +    noSkillsMatch: "Keine Skills entsprechen deiner Suche oder deinem Filter.",
      +    skillCount: "{count} Skill{s}",
      +    resultCount: "{count} Ergebnis{s}",
      +    noDescription: "Keine Beschreibung verfügbar.",
      +    toolsets: "Toolsets",
      +    toolsetLabel: "{name} Toolset",
      +    noToolsetsMatch: "Keine Toolsets entsprechen der Suche.",
      +    setupNeeded: "Einrichtung erforderlich",
      +    disabledForCli: "Für CLI deaktiviert",
      +    more: "+{count} weitere",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Filter",
      +    sections: "Bereiche",
      +    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 ihre Standardwerte zurücksetzen? Dies aktualisiert nur das Formular — Änderungen werden erst in config.yaml geschrieben, wenn du auf Speichern drückst.",
      +    resetScopeToast: "{scope} auf Standardwerte zurückgesetzt — überprüfen und Speichern, um zu übernehmen",
      +    rawYaml: "Rohe YAML-Konfiguration",
      +    searchResults: "Suchergebnisse",
      +    fields: "Feld{s}",
      +    noFieldsMatch: 'Keine Felder entsprechen "{query}"',
      +    configSaved: "Konfiguration gespeichert",
      +    yamlConfigSaved: "YAML-Konfiguration gespeichert",
      +    failedToSave: "Speichern fehlgeschlagen",
      +    failedToSaveYaml: "YAML konnte nicht gespeichert werden",
      +    failedToLoadRaw: "Rohe Konfiguration konnte nicht geladen werden",
      +    configImported: "Konfiguration importiert — überprüfen und speichern",
      +    invalidJson: "Ungültige JSON-Datei",
      +    categories: {
      +      general: "Allgemein",
      +      agent: "Agent",
      +      terminal: "Terminal",
      +      display: "Anzeige",
      +      delegation: "Delegation",
      +      memory: "Speicher",
      +      compression: "Komprimierung",
      +      security: "Sicherheit",
      +      browser: "Browser",
      +      voice: "Stimme",
      +      tts: "Text-zu-Sprache",
      +      stt: "Sprache-zu-Text",
      +      logging: "Protokollierung",
      +      discord: "Discord",
      +      auxiliary: "Hilfs",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "Änderungen werden sofort auf der Festplatte gespeichert. Aktive Sitzungen übernehmen neue Schlüssel automatisch.",
      +    confirmClearMessage:
      +      "Der gespeicherte Wert für diese Variable wird aus deiner .env-Datei entfernt. Dies kann über die UI nicht rückgängig gemacht werden.",
      +    confirmClearTitle: "Diesen Schlüssel löschen?",
      +    description: "Verwalte API-Schlüssel und Geheimnisse, die hier gespeichert sind",
      +    hideAdvanced: "Erweitert ausblenden",
      +    showAdvanced: "Erweitert anzeigen",
      +    llmProviders: "LLM-Anbieter",
      +    providersConfigured: "{configured} von {total} Anbietern konfiguriert",
      +    getKey: "Schlüssel holen",
      +    notConfigured: "{count} nicht konfiguriert",
      +    notSet: "Nicht gesetzt",
      +    keysCount: "{count} Schlüssel",
      +    enterValue: "Wert eingeben...",
      +    replaceCurrentValue: "Aktuellen Wert ersetzen ({preview})",
      +    showValue: "Echten Wert anzeigen",
      +    hideValue: "Wert ausblenden",
      +  },
      +
      +  oauth: {
      +    title: "Anbieter-Logins (OAuth)",
      +    providerLogins: "Anbieter-Logins (OAuth)",
      +    description: "{connected} von {total} OAuth-Anbietern verbunden. Login-Abläufe laufen derzeit über die CLI; klicke auf Befehl kopieren und füge ihn in ein Terminal ein, um einzurichten.",
      +    connected: "Verbunden",
      +    expired: "Abgelaufen",
      +    notConnected: "Nicht verbunden. Führe {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 extern / Fallback)",
      +    connect: "Verbinden",
      +    sessionExpires: "Sitzung läuft in {time} ab",
      +    initiatingLogin: "Login-Ablauf wird gestartet…",
      +    exchangingCode: "Code wird gegen Tokens getauscht…",
      +    connectedClosing: "Verbunden! Wird geschlossen…",
      +    loginFailed: "Anmeldung fehlgeschlagen.",
      +    sessionExpired: "Sitzung abgelaufen. Klicke auf Erneut versuchen, um eine neue Anmeldung zu starten.",
      +    reOpenAuth: "Authentifizierungsseite erneut öffnen",
      +    reOpenVerification: "Verifizierungsseite erneut öffnen",
      +    submitCode: "Code einreichen",
      +    pasteCode: "Autorisierungscode einfügen (mit #state-Suffix ist okay)",
      +    waitingAuth: "Warte, bis du im Browser autorisierst…",
      +    enterCodePrompt: "Ein neuer Tab wurde geöffnet. Gib bei Aufforderung diesen Code ein:",
      +    pkceStep1: "Ein neuer Tab wurde zu claude.ai geöffnet. Melde dich an und klicke auf Autorisieren.",
      +    pkceStep2: "Kopiere den Autorisierungscode, der nach der Autorisierung angezeigt wird.",
      +    pkceStep3: "Füge ihn unten ein und sende ab.",
      +    flowLabels: {
      +      pkce: "Browser-Login (PKCE)",
      +      device_code: "Gerätecode",
      +      external: "Externe CLI",
      +    },
      +    expiresIn: "läuft in {time} ab",
      +  },
      +
      +  language: {
      +    switchTo: "Zu Englisch wechseln",
      +  },
      +
      +  theme: {
      +    title: "Design",
      +    switchTheme: "Design wechseln",
      +  },
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Sammelbare Hermes-Abzeichen, verdient durch echten Sitzungsverlauf. Bekannte, noch nicht abgeschlossene Achievements werden als Entdeckt angezeigt; geheime Achievements bleiben verborgen, bis das erste passende Verhalten auftritt.",
      +      scan_subtitle:
      +        "Hermes-Sitzungsverlauf wird gescannt. Der erste Scan kann bei umfangreichem Verlauf 5–10 Sekunden dauern.",
      +    },
      +    actions: {
      +      rescan: "Neu scannen",
      +    },
      +    stats: {
      +      unlocked: "Freigeschaltet",
      +      unlocked_hint: "verdiente Abzeichen",
      +      discovered: "Entdeckt",
      +      discovered_hint: "bekannt, noch nicht verdient",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] bis zum ersten Signal",
      +      highest_tier: "Höchste Stufe",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Neueste",
      +      latest_hint_empty: "nutze Hermes mehr",
      +      none_yet: "Noch keine",
      +    },
      +    state: {
      +      unlocked: "Freigeschaltet",
      +      discovered: "Entdeckt",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Ziel {tier}",
      +      hidden: "Verborgen",
      +      complete: "Abgeschlossen",
      +      objective: "Ziel",
      +    },
      +    progress: {
      +      hidden: "verborgen",
      +    },
      +    scan: {
      +      building_headline: "Achievement-Profil wird erstellt…",
      +      building_detail:
      +        "Sitzungen, Tool-Aufrufe, Modell-Metadaten und Freischaltstatus werden gelesen.",
      +      starting_headline: "Achievement-Scan wird gestartet…",
      +      progress_detail:
      +        "{scanned} von {total} Sitzungen gescannt · {pct}%. Abzeichen werden freigeschaltet, sobald mehr Verlauf eingelesen wird.",
      +      idle_detail:
      +        "Sitzungen, Tool-Aufrufe, Modell-Metadaten und Freischaltstatus werden gelesen. Abzeichen erscheinen hier, sobald sie freigeschaltet werden.",
      +    },
      +    guide: {
      +      tiers_header: "Stufen",
      +      secret_header: "[redacted:secret-assignment] Achievements",
      +      secret_body:
      +        "Geheimnisse verbergen ihren genauen Auslöser. Sobald Hermes ein verwandtes Signal erkennt, wird die Karte zu Entdeckt und zeigt ihre Anforderung an.",
      +      scan_status_header: "Scan-Status",
      +      scan_status_body:
      +        "Hermes scannt den lokalen Verlauf einmalig, danach erscheinen die Karten automatisch. Es ist nichts hängengeblieben, wenn dies ein paar Sekunden dauert.",
      +      what_scanned_header: "Was gescannt wird",
      +      what_scanned_body:
      +        "Sitzungen, Tool-Aufrufe, Modell-Metadaten, Fehler, Achievements und lokaler Freischaltstatus.",
      +    },
      +    card: {
      +      share_title: "Dieses Achievement teilen",
      +      share_label: "{name} teilen",
      +      share_text: "Teilen",
      +      how_to_reveal: "Wie aufdecken",
      +      what_counts: "Was zählt",
      +      evidence_label: "Beleg",
      +      evidence_session_fallback: "Sitzung",
      +      no_evidence: "Noch kein Beleg",
      +    },
      +    latest: {
      +      header: "Letzte Freischaltungen",
      +    },
      +    empty: {
      +      no_secrets_header: "Keine verborgenen Geheimnisse mehr in diesem Scan.",
      +      no_secrets_body:
      +        "Hinweis: Geheimnisse beginnen meist bei ungewöhnlichen Fehlern oder Power-User-Mustern – Port-Konflikten, Berechtigungswänden, fehlenden Umgebungsvariablen, YAML-Fehlern, Docker-Kollisionen, Rollback-/Checkpoint-Nutzung, Cache-Treffern oder kleinen Fixes nach viel rotem Text.",
      +    },
      +    filters: {
      +      all_categories: "Alle",
      +      visibility_all: "alle",
      +      visibility_unlocked: "freigeschaltet",
      +      visibility_discovered: "entdeckt",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Achievement teilen",
      +      header: "Teilen: {name}",
      +      close: "Schließen",
      +      rendering: "Wird gerendert…",
      +      card_alt: "{name} Share-Karte",
      +      error_generic: "Etwas ist schiefgelaufen.",
      +      x_title: "Öffnet X mit einem vorgefertigten Post",
      +      x_button: "Auf X teilen",
      +      copy_title: "Bild kopieren, um es in deinen Post einzufügen",
      +      copy_button: "Bild kopieren",
      +      copied: "Kopiert ✓",
      +      download_button: "PNG herunterladen",
      +      hint:
      +        "Auf X teilen öffnet einen vorgefertigten Post in einem neuen Tab. Klicke zuerst auf Bild kopieren, wenn du das 1200×630-Abzeichen anhängen möchtest – X lässt dich es direkt in den Tweet-Editor einfügen. PNG herunterladen speichert die Datei zur Nutzung an beliebiger Stelle.",
      +      clipboard_unsupported:
      +        "Bildkopie über die Zwischenablage wird in diesem Browser nicht unterstützt – nutze stattdessen Herunterladen.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Kanban-Board wird geladen…",
      +    loadFailed: "Laden des Kanban-Boards fehlgeschlagen: ",
      +    loadFailedHint:
      +      "Das Backend erstellt kanban.db beim ersten Lesen automatisch. Wenn das Problem bestehen bleibt, prüfe die Dashboard-Logs.",
      +    board: "Board",
      +    newBoard: "+ Neues Board",
      +    newBoardTitle: "Neues Board",
      +    newBoardDescription:
      +      "Mit Boards kannst du voneinander unabhängige Arbeitsabläufe trennen — eines pro Projekt, Repository oder Domäne. Worker auf einem Board sehen niemals die Aufgaben eines anderen Boards.",
      +    slug: "Slug",
      +    slugHint: "— Kleinbuchstaben, Bindestriche, z. B. atm10-server",
      +    displayName: "Anzeigename",
      +    displayNameHint: "(optional)",
      +    description: "Beschreibung",
      +    descriptionHint: "(optional)",
      +    icon: "Symbol",
      +    iconHint: "(einzelnes Zeichen oder Emoji)",
      +    switchAfterCreate: "Nach dem Erstellen zu diesem Board wechseln",
      +    cancel: "Abbrechen",
      +    creating: "Wird erstellt…",
      +    createBoard: "Board erstellen",
      +    search: "Suchen",
      +    filterCards: "Karten filtern…",
      +    tenant: "Tenant",
      +    allTenants: "Alle Tenants",
      +    assignee: "Zuständige Person",
      +    allProfiles: "Alle Profile",
      +    showArchived: "Archivierte anzeigen",
      +    lanesByProfile: "Spuren nach Profil",
      +    nudgeDispatcher: "Dispatcher anstoßen",
      +    refresh: "Aktualisieren",
      +    selected: "ausgewählt",
      +    complete: "Abschließen",
      +    archive: "Archivieren",
      +    apply: "Anwenden",
      +    clear: "Zurücksetzen",
      +    createTask: "Aufgabe in dieser Spalte erstellen",
      +    noTasks: "— keine Aufgaben —",
      +    unassigned: "nicht zugewiesen",
      +    untitled: "(ohne Titel)",
      +    loadingDetail: "Wird geladen…",
      +    addComment: "Kommentar hinzufügen… (Enter zum Senden)",
      +    comment: "Kommentar",
      +    status: "Status",
      +    workspace: "Arbeitsbereich",
      +    skills: "Fähigkeiten",
      +    createdBy: "Erstellt von",
      +    result: "Ergebnis",
      +    comments: "Kommentare",
      +    events: "Ereignisse",
      +    runHistory: "Ausführungsverlauf",
      +    workerLog: "Worker-Log",
      +    loadingLog: "Log wird geladen…",
      +    noWorkerLog:
      +      "— noch kein Worker-Log (Aufgabe wurde nicht gestartet oder Log wurde rotiert) —",
      +    noDescription: "— keine Beschreibung —",
      +    noComments: "— keine Kommentare —",
      +    edit: "bearbeiten",
      +    save: "Speichern",
      +    dependencies: "Abhängigkeiten",
      +    parents: "Übergeordnet:",
      +    children: "Untergeordnet:",
      +    none: "keine",
      +    addParent: "— übergeordnete Aufgabe hinzufügen —",
      +    addChild: "— untergeordnete Aufgabe hinzufügen —",
      +    removeDependency: "Abhängigkeit entfernen",
      +    block: "Blockieren",
      +    unblock: "Freigeben",
      +    notifyHomeChannels: "Home-Kanäle benachrichtigen",
      +    diagnostics: "Diagnose",
      +    hide: "Ausblenden",
      +    show: "Anzeigen",
      +    attention: "Achtung",
      +    tasksNeedAttention: "Aufgaben benötigen Aufmerksamkeit",
      +    taskNeedsAttention: "1 Aufgabe benötigt Aufmerksamkeit",
      +    diagnostic: "Diagnose",
      +    open: "Öffnen",
      +    close: "Schließen (Esc)",
      +    reassignTo: "Neu zuweisen an:",
      +    copied: "Kopiert",
      +    copyCommand: "Befehl in die Zwischenablage kopieren",
      +    reclaim: "Zurückholen",
      +    reassign: "Neu zuweisen",
      +    renderingError: "Im Kanban-Tab ist ein Renderfehler aufgetreten",
      +    reloadView: "Ansicht neu laden",
      +    wsAuthFailed:
      +      "WebSocket-Authentifizierung fehlgeschlagen — lade die Seite neu, um das Sitzungs-Token zu aktualisieren.",
      +    markDone: "{n} Aufgabe(n) als erledigt markieren?",
      +    markArchived: "{n} Aufgabe(n) archivieren?",
      +    warning: "Warnung",
      +    phantomIds: "Phantom-IDs:",
      +    active: "aktiv",
      +    ended: "beendet",
      +    noProfile: "(kein Profil)",
      +    showAllAttempts: "Alle Versuche anzeigen",
      +    sendingUpdates: "Aktualisierungen werden gesendet an ",
      +    sendNotifications: "Benachrichtigungen für Abgeschlossen / Blockiert / Aufgegeben senden an",
      +    archiveBoardConfirm:
      +      "Board „{name}“ archivieren? Es wird nach boards/_archived/ verschoben, sodass du es später wiederherstellen kannst. Aufgaben auf diesem Board erscheinen nirgendwo mehr in der UI.",
      +    archiveBoardTitle: "Dieses Board archivieren",
      +    boardSwitcherHint: "Mit Boards kannst du voneinander unabhängige Arbeitsabläufe trennen",
      +    taskCreatedWarning: "Aufgabe erstellt, aber: ",
      +    moveFailed: "Verschieben fehlgeschlagen: ",
      +    bulkFailed: "Bulk: ",
      +    completionBlockedHallucination: "⚠ Abschluss blockiert — Phantom-Karten-IDs",
      +    suspectedHallucinatedReferences: "⚠ Text verweist auf Phantom-Karten-IDs",
      +    pickProfileFirst: "Wähle zuerst ein Profil aus.",
      +    unblockedMessage: "{id} freigegeben. Aufgabe ist bereit für den nächsten Tick.",
      +    unblockFailed: "Freigeben fehlgeschlagen: ",
      +    reclaimedMessage: "{id} zurückgeholt. Aufgabe ist wieder auf ready.",
      +    reclaimFailed: "Zurückholen fehlgeschlagen: ",
      +    reassignedMessage: "{id} an {profile} neu zugewiesen.",
      +    reassignFailed: "Neu zuweisen fehlgeschlagen: ",
      +    selectForBulk: "Für Bulk-Aktionen auswählen",
      +    clickToEdit: "Zum Bearbeiten klicken",
      +    clickToEditAssignee: "Klicken, um zuständige Person zu bearbeiten",
      +    emptyAssignee: "(leer = Zuweisung aufheben)",
      +    columnLabels: {
      +      triage: "Triage",
      +      todo: "Zu erledigen",
      +      ready: "Bereit",
      +      running: "In Bearbeitung",
      +      blocked: "Blockiert",
      +      done: "Erledigt",
      +      archived: "Archiviert",
      +    },
      +    columnHelp: {
      +      triage: "Rohe Ideen — ein Specifier wird die Spezifikation ausarbeiten",
      +      todo: "Wartet auf Abhängigkeiten oder ist nicht zugewiesen",
      +      ready: "Zugewiesen und wartet auf einen Dispatcher-Tick",
      +      running: "Von einem Worker übernommen — in Bearbeitung",
      +      blocked: "Worker hat um menschliche Eingabe gebeten",
      +      done: "Abgeschlossen",
      +      archived: "Archiviert",
      +    },
      +    confirmDone:
      +      "Diese Aufgabe als erledigt markieren? Der Anspruch des Workers wird freigegeben und abhängige untergeordnete Aufgaben werden bereit.",
      +    confirmArchive:
      +      "Diese Aufgabe archivieren? Sie verschwindet aus der Standard-Board-Ansicht.",
      +    confirmBlocked:
      +      "Diese Aufgabe als blockiert markieren? Der Anspruch des Workers wird freigegeben.",
      +    completionSummary:
      +      "Abschluss-Zusammenfassung für {label}. Diese wird als Ergebnis der Aufgabe gespeichert.",
      +    completionSummaryRequired:
      +      "Eine Abschluss-Zusammenfassung ist erforderlich, bevor eine Aufgabe als erledigt markiert werden kann.",
      +    triagePlaceholder: "Grobe Idee — die KI wird die Spezifikation erstellen…",
      +    taskTitlePlaceholder: "Titel der neuen Aufgabe…",
      +    specifier: "Specifier",
      +    assigneePlaceholder: "Zuständige Person",
      +    priority: "Priorität",
      +    skillsPlaceholder:
      +      "Fähigkeiten (optional, kommagetrennt): translation, github-code-review",
      +    noParent: "— keine übergeordnete Aufgabe —",
      +    workspacePathDir: "Arbeitsbereichs-Pfad (erforderlich, z. B. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "Arbeitsbereichs-Pfad (optional, wird aus zuständiger Person abgeleitet, wenn leer)",
      +    logTruncated: "(zeige die letzten 100 KB — vollständiges Log unter ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/en.ts
      @@ -426,4 +426,272 @@ export const en: Translations = {
           title: "Theme",
           switchTheme: "Switch theme",
         },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Collectible Hermes badges earned from real session history. Known unfinished achievements are shown as Discovered; Secret achievements stay hidden until the first matching behavior appears.",
      +      scan_subtitle:
      +        "Scanning Hermes session history. First scan can take 5–10 seconds on large histories.",
      +    },
      +    actions: {
      +      rescan: "Rescan",
      +    },
      +    stats: {
      +      unlocked: "Unlocked",
      +      unlocked_hint: "earned badges",
      +      discovered: "Discovered",
      +      discovered_hint: "known, not earned yet",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] until first signal",
      +      highest_tier: "Highest tier",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Latest",
      +      latest_hint_empty: "run Hermes more",
      +      none_yet: "None yet",
      +    },
      +    state: {
      +      unlocked: "Unlocked",
      +      discovered: "Discovered",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Target {tier}",
      +      hidden: "Hidden",
      +      complete: "Complete",
      +      objective: "Objective",
      +    },
      +    progress: {
      +      hidden: "hidden",
      +    },
      +    scan: {
      +      building_headline: "Building achievement profile…",
      +      building_detail:
      +        "Reading sessions, tool calls, model metadata, and unlock state.",
      +      starting_headline: "Starting achievement scan…",
      +      progress_detail:
      +        "Scanned {scanned} of {total} sessions · {pct}%. Badges unlock as more history streams in.",
      +      idle_detail:
      +        "Reading sessions, tool calls, model metadata, and unlock state. Badges appear here as they unlock.",
      +    },
      +    guide: {
      +      tiers_header: "Tiers",
      +      secret_header: "[redacted:secret-assignment] achievements",
      +      secret_body:
      +        "Secrets hide their exact trigger. Once Hermes sees a related signal, the card becomes Discovered and shows its requirement.",
      +      scan_status_header: "Scan status",
      +      scan_status_body:
      +        "Hermes is scanning local history once, then cards will appear automatically. Nothing is stuck if this takes a few seconds.",
      +      what_scanned_header: "What is scanned",
      +      what_scanned_body:
      +        "Sessions, tool calls, model metadata, errors, achievements, and local unlock state.",
      +    },
      +    card: {
      +      share_title: "Share this achievement",
      +      share_label: "Share {name}",
      +      share_text: "Share",
      +      how_to_reveal: "How to reveal",
      +      what_counts: "What counts",
      +      evidence_label: "Evidence",
      +      evidence_session_fallback: "session",
      +      no_evidence: "No evidence yet",
      +    },
      +    latest: {
      +      header: "Recent unlocks",
      +    },
      +    empty: {
      +      no_secrets_header: "No hidden secrets left in this scan.",
      +      no_secrets_body:
      +        "Clue: secrets usually start from unusual failure or power-user patterns — port conflicts, permission walls, missing env vars, YAML mistakes, Docker collisions, rollback/checkpoint use, cache hits, or tiny fixes after lots of red text.",
      +    },
      +    filters: {
      +      all_categories: "All",
      +      visibility_all: "all",
      +      visibility_unlocked: "unlocked",
      +      visibility_discovered: "discovered",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Share achievement",
      +      header: "Share: {name}",
      +      close: "Close",
      +      rendering: "Rendering…",
      +      card_alt: "{name} share card",
      +      error_generic: "Something went wrong.",
      +      x_title: "Opens X with a pre-filled post",
      +      x_button: "Share on X",
      +      copy_title: "Copy the image to paste into your post",
      +      copy_button: "Copy image",
      +      copied: "Copied ✓",
      +      download_button: "Download PNG",
      +      hint:
      +        "Share on X opens a pre-filled post in a new tab. Click Copy image first if you want the 1200×630 badge attached — X lets you paste it right into the tweet composer. Download PNG saves the file for use anywhere.",
      +      clipboard_unsupported:
      +        "Clipboard image copy not supported in this browser — use Download instead.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +
      +  kanban: {
      +    loading: "Loading Kanban board…",
      +    loadFailed: "Failed to load Kanban board: ",
      +    loadFailedHint:
      +      "The backend auto-creates kanban.db on first read. If this persists, check the dashboard logs.",
      +    board: "Board",
      +    newBoard: "+ New board",
      +    newBoardTitle: "New board",
      +    newBoardDescription:
      +      "Boards let you separate unrelated streams of work — one per project, repo, or domain. Workers on one board never see another board's tasks.",
      +    slug: "Slug",
      +    slugHint: "— lowercase, hyphens, e.g. atm10-server",
      +    displayName: "Display name",
      +    displayNameHint: "(optional)",
      +    description: "Description",
      +    descriptionHint: "(optional)",
      +    icon: "Icon",
      +    iconHint: "(single character or emoji)",
      +    switchAfterCreate: "Switch to this board after creating it",
      +    cancel: "Cancel",
      +    creating: "Creating…",
      +    createBoard: "Create board",
      +    search: "Search",
      +    filterCards: "Filter cards…",
      +    tenant: "Tenant",
      +    allTenants: "All tenants",
      +    assignee: "Assignee",
      +    allProfiles: "All profiles",
      +    showArchived: "Show archived",
      +    lanesByProfile: "Lanes by profile",
      +    nudgeDispatcher: "Nudge dispatcher",
      +    refresh: "Refresh",
      +    selected: "selected",
      +    complete: "Complete",
      +    archive: "Archive",
      +    apply: "Apply",
      +    clear: "Clear",
      +    createTask: "Create task in this column",
      +    noTasks: "— no tasks —",
      +    unassigned: "unassigned",
      +    untitled: "(untitled)",
      +    loadingDetail: "Loading…",
      +    addComment: "Add a comment… (Enter to submit)",
      +    comment: "Comment",
      +    status: "Status",
      +    workspace: "Workspace",
      +    skills: "Skills",
      +    createdBy: "Created by",
      +    result: "Result",
      +    comments: "Comments",
      +    events: "Events",
      +    runHistory: "Run history",
      +    workerLog: "Worker log",
      +    loadingLog: "Loading log…",
      +    noWorkerLog:
      +      "— no worker log yet (task hasn't spawned or log was rotated away) —",
      +    noDescription: "— no description —",
      +    noComments: "— no comments —",
      +    edit: "edit",
      +    save: "Save",
      +    dependencies: "Dependencies",
      +    parents: "Parents:",
      +    children: "Children:",
      +    none: "none",
      +    addParent: "— add parent —",
      +    addChild: "— add child —",
      +    removeDependency: "Remove dependency",
      +    block: "Block",
      +    unblock: "Unblock",
      +    notifyHomeChannels: "Notify home channels",
      +    diagnostics: "Diagnostics",
      +    hide: "Hide",
      +    show: "Show",
      +    attention: "Attention",
      +    tasksNeedAttention: "tasks need attention",
      +    taskNeedsAttention: "1 task needs attention",
      +    diagnostic: "diagnostic",
      +    open: "Open",
      +    close: "Close (Esc)",
      +    reassignTo: "Reassign to:",
      +    copied: "Copied",
      +    copyCommand: "Copy command to clipboard",
      +    reclaim: "Reclaim",
      +    reassign: "Reassign",
      +    renderingError: "Kanban tab hit a rendering error",
      +    reloadView: "Reload view",
      +    wsAuthFailed:
      +      "WebSocket auth failed — reload the page to refresh the session token.",
      +    markDone: "Mark {n} task(s) as done?",
      +    markArchived: "Archive {n} task(s)?",
      +    warning: "Warning",
      +    phantomIds: "Phantom ids:",
      +    active: "active",
      +    ended: "ended",
      +    noProfile: "(no profile)",
      +    showAllAttempts: "Show all attempts",
      +    sendingUpdates: "Sending updates to",
      +    sendNotifications: "Send completed / blocked / gave_up notifications to",
      +    archiveBoardConfirm:
      +      "Archive board '{name}'? It will be moved to boards/_archived/ so you can recover it later. Tasks on this board will no longer appear anywhere in the UI.",
      +    archiveBoardTitle: "Archive this board",
      +    boardSwitcherHint: "Boards let you separate unrelated streams of work",
      +    taskCreatedWarning: "Task created, but: ",
      +    moveFailed: "Move failed: ",
      +    bulkFailed: "Bulk: ",
      +    completionBlockedHallucination: "⚠ Completion blocked — phantom card ids",
      +    suspectedHallucinatedReferences: "⚠ Prose referenced phantom card ids",
      +    pickProfileFirst: "Pick a profile first.",
      +    unblockedMessage: "Unblocked {id}. Task is ready for the next tick.",
      +    unblockFailed: "Unblock failed: ",
      +    reclaimedMessage: "Reclaimed {id}. Task is back to ready.",
      +    reclaimFailed: "Reclaim failed: ",
      +    reassignedMessage: "Reassigned {id} to {profile}.",
      +    reassignFailed: "Reassign failed: ",
      +    selectForBulk: "Select for bulk actions",
      +    clickToEdit: "Click to edit",
      +    clickToEditAssignee: "Click to edit assignee",
      +    emptyAssignee: "(empty = unassign)",
      +    columnLabels: {
      +      triage: "Triage",
      +      todo: "Todo",
      +      ready: "Ready",
      +      running: "In Progress",
      +      blocked: "Blocked",
      +      done: "Done",
      +      archived: "Archived",
      +    },
      +    columnHelp: {
      +      triage: "Raw ideas — a specifier will flesh out the spec",
      +      todo: "Waiting on dependencies or unassigned",
      +      ready: "Assigned and waiting for a dispatcher tick",
      +      running: "Claimed by a worker — in-flight",
      +      blocked: "Worker asked for human input",
      +      done: "Completed",
      +      archived: "Archived",
      +    },
      +    confirmDone:
      +      "Mark this task as done? The worker's claim is released and dependent children become ready.",
      +    confirmArchive:
      +      "Archive this task? It disappears from the default board view.",
      +    confirmBlocked:
      +      "Mark this task as blocked? The worker's claim is released.",
      +    completionSummary:
      +      "Completion summary for {label}. This is stored as the task result.",
      +    completionSummaryRequired:
      +      "Completion summary is required before marking a task done.",
      +    triagePlaceholder: "Rough idea — AI will spec it…",
      +    taskTitlePlaceholder: "New task title…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "assignee",
      +    priority: "Priority",
      +    skillsPlaceholder:
      +      "skills (optional, comma-separated): translation, github-code-review",
      +    noParent: "— no parent —",
      +    workspacePathDir: "workspace path (required, e.g. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "workspace path (optional, derived from assignee if blank)",
      +    logTruncated: "(showing last 100 KB — full log at ",
      +    logAt: ")",
      +  },
       };
    • web/src/i18n/es.ts
      @@ -0,0 +1,695 @@
      +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: "msjs",
      +    tools: "herramientas",
      +    match: "coincidencia",
      +    other: "Otros",
      +    configured: "configurado",
      +    removed: "eliminado",
      +    failedToToggle: "No se pudo alternar",
      +    failedToRemove: "No se pudo eliminar",
      +    failedToReveal: "No se pudo mostrar",
      +    collapse: "Contraer",
      +    expand: "Expandir",
      +    general: "General",
      +    messaging: "Mensajería",
      +    pluginLoadFailed:
      +      "No se pudo cargar el script de este complemento. Revisa la pestaña Network (dashboard-plugins/…) y la ruta del complemento del servidor.",
      +    pluginNotRegistered:
      +      "El script del complemento no llamó a register(), o falló. 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: "Inicio fallido",
      +      off: "Apagado",
      +      running: "En ejecución",
      +      starting: "Iniciando",
      +      stopped: "Detenido",
      +    },
      +    nav: {
      +      analytics: "Analíticas",
      +      chat: "Chat",
      +      config: "Configuración",
      +      cron: "Cron",
      +      documentation: "Documentación",
      +      keys: "Claves",
      +      logs: "Registros",
      +      models: "Modelos",
      +      profiles: "perfiles : multi agentes",
      +      plugins: "Complementos",
      +      sessions: "Sesiones",
      +      skills: "Habilidades",
      +    },
      +    modelToolsSheetSubtitle: "y herramientas",
      +    modelToolsSheetTitle: "Modelo",
      +    navigation: "Navegación",
      +    openDocumentation: "Abrir documentación en una nueva pestaña",
      +    openNavigation: "Abrir navegación",
      +    pluginNavSection: "Complementos",
      +    sessionsActiveCount: "{count} activas",
      +    statusOverview: "Resumen de estado",
      +    system: "Sistema",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Acción fallida",
      +    actionFinished: "Finalizado",
      +    actions: "Acciones",
      +    agent: "Agente",
      +    activeSessions: "Sesiones activas",
      +    connected: "Conectado",
      +    connectedPlatforms: "Plataformas conectadas",
      +    disconnected: "Desconectado",
      +    error: "Error",
      +    failed: "Fallido",
      +    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: "Inicio fallido",
      +    starting: "Iniciando",
      +    startedInBackground: "Iniciado en segundo plano — revisa los registros para ver el progreso",
      +    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: "Ninguna sesión coincide con tu búsqueda",
      +    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 elimina permanentemente la conversación y todos sus mensajes. No se puede deshacer.",
      +    sessionDeleted: "Sesión eliminada",
      +    failedToDelete: "No se pudo eliminar la sesión",
      +    resumeInChat: "Reanudar en el chat",
      +    previousPage: "Página anterior",
      +    nextPage: "Página siguiente",
      +    roles: {
      +      user: "Usuario",
      +      assistant: "Asistente",
      +      system: "Sistema",
      +      tool: "Herramienta",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Período:",
      +    totalTokens: "[redacted:secret-assignment] 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: "Agente cargó",
      +    edits: "Agente gestionó",
      +    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: "[redacted:secret-assignment]",
      +    perDayAvg: "/día prom.",
      +    acrossModels: "en {count} modelos",
      +    inOut: "{input} entrada / {output} salida",
      +  },
      +
      +  models: {
      +    modelsUsed: "Modelos utilizados",
      +    estimatedCost: "Coste est.",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "sesiones",
      +    avgPerSession: "prom./sesión",
      +    apiCalls: "llamadas API",
      +    toolCalls: "llamadas de herramientas",
      +    noModelsData: "No hay datos de uso de modelos para este período",
      +    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 líneas de registro",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Esto elimina la tarea de la programación. No se puede deshacer.",
      +    confirmDeleteTitle: "¿Eliminar tarea programada?",
      +    newJob: "Nueva tarea Cron",
      +    nameOptional: "Nombre (opcional)",
      +    namePlaceholder: "p. ej. Resumen diario",
      +    prompt: "Prompt",
      +    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 cron configuradas. Crea una arriba.",
      +    last: "Última",
      +    next: "Próxima",
      +    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, etc.",
      +    nameRequired: "El nombre es obligatorio",
      +    nameRule:
      +      "Solo letras minúsculas, dígitos, _ y -; debe comenzar con una letra o dígito; hasta 64 caracteres.",
      +    invalidName: "Nombre de perfil no válido",
      +    cloneFromDefault: "Clonar configuración del perfil predeterminado",
      +    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 / prompt del sistema)",
      +    soulPlaceholder: "# Cómo debe 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 elimina permanentemente el perfil '{name}' — configuración, claves, memorias, sesiones, habilidades, tareas cron. No se puede deshacer.",
      +    created: "Creado",
      +    deleted: "Eliminado",
      +    renamed: "Renombrado",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Motor de contexto",
      +    dashboardSlots: "Slots del panel",
      +    disableRuntime: "Deshabilitar",
      +    enableAfterInstall: "Habilitar tras instalar",
      +    enableRuntime: "Habilitar",
      +    forceReinstall: "Forzar reinstalación (eliminar carpeta existente primero)",
      +    headline:
      +      "Descubre, instala, habilita y actualiza complementos de Hermes (equivalente a `hermes plugins`).",
      +    identifierLabel: "URL de Git u owner/repo",
      +    inactive: "inactivo",
      +    installBtn: "Instalar desde Git",
      +    installHeading: "Instalar desde GitHub / URL de Git",
      +    installHint: "Usa la forma corta owner/repo o una URL de clonación https:// o git@ completa.",
      +    memoryProviderLabel: "Proveedor de memoria",
      +    missingEnvWarn: "Configura estos en Claves antes de que el complemento pueda ejecutarse:",
      +    noDashboardTab: "Sin pestaña de panel",
      +    openTab: "Abrir",
      +    orphanHeading: "Extensiones solo del panel (sin coincidencia de plugin.yaml del agente)",
      +    pluginListHeading: "Complementos instalados",
      +    providerDefaults: "incorporado / predeterminado",
      +    providersHeading: "Complementos de proveedor en tiempo de ejecución",
      +    providersHint:
      +      "Escribe memory.provider (vacío = incorporado) y context.engine en config.yaml. Surte efecto en la próxima sesión.",
      +    refreshDashboard: "Volver a escanear extensiones del panel",
      +    removeConfirm: "¿Eliminar este complemento de ~/.hermes/plugins/?",
      +    removeHint: "Solo se pueden eliminar complementos instalados por el usuario en ~/.hermes/plugins.",
      +    rescanHeading: "Registro de complementos SPA",
      +    rescanHint: "Vuelve a escanear tras añadir archivos en disco para que la barra lateral del panel detecte nuevos manifiestos.",
      +    runtimeHeading: "Tiempo de ejecución del Gateway (complementos YAML)",
      +    saveProviders: "Guardar configuración del proveedor",
      +    savedProviders: "Configuración del proveedor guardada.",
      +    sourceBadge: "Fuente",
      +    authRequired: "Autenticación requerida",
      +    authRequiredHint: "Ejecuta este comando para autenticarte:",
      +    updateGit: "Git pull",
      +    versionBadge: "Versión",
      +    showInSidebar: "Mostrar en barra lateral",
      +    hideFromSidebar: "Ocultar de la barra lateral",
      +  },
      +
      +  skills: {
      +    title: "Habilidades",
      +    searchPlaceholder: "Buscar habilidades y conjuntos de herramientas...",
      +    enabledOf: "{enabled}/{total} habilitados",
      +    all: "Todas",
      +    categories: "Categorías",
      +    filters: "Filtros",
      +    noSkills: "No se encontraron habilidades. Las habilidades se cargan desde ~/.hermes/skills/",
      +    noSkillsMatch: "Ninguna habilidad coincide con tu búsqueda o filtro.",
      +    skillCount: "{count} habilidad{s}",
      +    resultCount: "{count} resultado{s}",
      +    noDescription: "No hay descripción disponible.",
      +    toolsets: "Conjuntos de herramientas",
      +    toolsetLabel: "conjunto de herramientas {name}",
      +    noToolsetsMatch: "Ningún conjunto de herramientas 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 predeterminados",
      +    resetScopeTooltip: "Restablecer {scope} a los valores predeterminados",
      +    confirmResetScope: "¿Restablecer todos los ajustes de {scope} a sus valores predeterminados? Esto solo actualiza el formulario — los cambios no se escriben en config.yaml hasta que pulses Guardar.",
      +    resetScopeToast: "{scope} restablecido a los valores predeterminados — revisa y guarda para que persista",
      +    rawYaml: "Configuración YAML en bruto",
      +    searchResults: "Resultados de búsqueda",
      +    fields: "campo{s}",
      +    noFieldsMatch: 'Ningún campo coincide con "{query}"',
      +    configSaved: "Configuración guardada",
      +    yamlConfigSaved: "Configuración YAML guardada",
      +    failedToSave: "No se pudo guardar",
      +    failedToSaveYaml: "No se pudo guardar YAML",
      +    failedToLoadRaw: "No se pudo cargar la configuración en bruto",
      +    configImported: "Configuración importada — revisa y guarda",
      +    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 adoptan las nuevas claves automáticamente.",
      +    confirmClearMessage:
      +      "El valor almacenado para esta variable se eliminará de tu archivo .env. Esto no se puede deshacer desde la UI.",
      +    confirmClearTitle: "¿Limpiar 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{s}",
      +    enterValue: "Introduce un 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 actualmente se ejecutan a través de la CLI; haz clic en Copiar comando y pégalo en una terminal para configurar.",
      +    connected: "Conectado",
      +    expired: "Caducado",
      +    notConnected: "No conectado. Ejecuta {command} en una terminal.",
      +    runInTerminal: "en una terminal.",
      +    noProviders: "No se han detectado proveedores compatibles con OAuth.",
      +    login: "Iniciar sesión",
      +    disconnect: "Desconectar",
      +    managedExternally: "Gestionado externamente",
      +    copied: "Copiado ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Copiar comando CLI (para externo / alternativa)",
      +    connect: "Conectar",
      +    sessionExpires: "La sesión caduca en {time}",
      +    initiatingLogin: "Iniciando flujo de inicio de sesión…",
      +    exchangingCode: "Intercambiando código por tokens…",
      +    connectedClosing: "¡Conectado! Cerrando…",
      +    loginFailed: "Inicio de sesión fallido.",
      +    sessionExpired: "Sesión caducada. Haz clic en Reintentar para iniciar un nuevo inicio de sesión.",
      +    reOpenAuth: "Reabrir página de autenticación",
      +    reOpenVerification: "Reabrir página de verificación",
      +    submitCode: "Enviar código",
      +    pasteCode: "Pega el código de autorización (con el sufijo #state está bien)",
      +    waitingAuth: "Esperando que autorices en el navegador…",
      +    enterCodePrompt: "Se abrió una nueva pestaña. Introduce este código si se 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 mostrado tras autorizar.",
      +    pkceStep3: "Pégalo abajo y envía.",
      +    flowLabels: {
      +      pkce: "Inicio de sesión por navegador (PKCE)",
      +      device_code: "Código de dispositivo",
      +      external: "CLI externa",
      +    },
      +    expiresIn: "caduca en {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Cambiar a inglés",
      +  },
      +
      +  theme: {
      +    title: "Tema",
      +    switchTheme: "Cambiar tema",
      +  },
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Insignias coleccionables de Hermes ganadas a partir del historial real de sesiones. Los logros conocidos no completados se muestran como Descubiertos; los logros secretos permanecen ocultos hasta que aparece el primer comportamiento coincidente.",
      +      scan_subtitle:
      +        "Escaneando el historial de sesiones de Hermes. El primer escaneo puede tardar 5–10 segundos en historiales grandes.",
      +    },
      +    actions: {
      +      rescan: "Volver a escanear",
      +    },
      +    stats: {
      +      unlocked: "Desbloqueados",
      +      unlocked_hint: "insignias ganadas",
      +      discovered: "Descubiertos",
      +      discovered_hint: "conocidos, aún no ganados",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] hasta la primera señal",
      +      highest_tier: "Nivel más alto",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Más reciente",
      +      latest_hint_empty: "usa Hermes más",
      +      none_yet: "Ninguno aún",
      +    },
      +    state: {
      +      unlocked: "Desbloqueado",
      +      discovered: "Descubierto",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Objetivo {tier}",
      +      hidden: "Oculto",
      +      complete: "Completo",
      +      objective: "Objetivo",
      +    },
      +    progress: {
      +      hidden: "oculto",
      +    },
      +    scan: {
      +      building_headline: "Construyendo perfil de logros…",
      +      building_detail:
      +        "Leyendo sesiones, llamadas a herramientas, metadatos del modelo y estado de desbloqueo.",
      +      starting_headline: "Iniciando escaneo de logros…",
      +      progress_detail:
      +        "Escaneadas {scanned} de {total} sesiones · {pct}%. Las insignias se desbloquean a medida que se procesa más historial.",
      +      idle_detail:
      +        "Leyendo sesiones, llamadas a herramientas, metadatos del modelo y estado de desbloqueo. Las insignias aparecerán aquí a medida que se desbloqueen.",
      +    },
      +    guide: {
      +      tiers_header: "Niveles",
      +      secret_header: "[redacted:secret-assignment] secretos",
      +      secret_body:
      +        "Los secretos ocultan su disparador exacto. Una vez que Hermes detecta una señal relacionada, la tarjeta pasa a Descubierto y muestra su requisito.",
      +      scan_status_header: "Estado del escaneo",
      +      scan_status_body:
      +        "Hermes está escaneando el historial local una vez, después las tarjetas aparecerán automáticamente. No hay nada bloqueado si tarda unos segundos.",
      +      what_scanned_header: "Qué se escanea",
      +      what_scanned_body:
      +        "Sesiones, llamadas a herramientas, metadatos del modelo, errores, logros y estado de desbloqueo local.",
      +    },
      +    card: {
      +      share_title: "Compartir este logro",
      +      share_label: "Compartir {name}",
      +      share_text: "Compartir",
      +      how_to_reveal: "Cómo revelarlo",
      +      what_counts: "Qué cuenta",
      +      evidence_label: "Evidencia",
      +      evidence_session_fallback: "sesión",
      +      no_evidence: "Aún sin evidencia",
      +    },
      +    latest: {
      +      header: "Desbloqueos recientes",
      +    },
      +    empty: {
      +      no_secrets_header: "No quedan secretos ocultos en este escaneo.",
      +      no_secrets_body:
      +        "Pista: los secretos suelen comenzar a partir de fallos inusuales o patrones de usuario avanzado: conflictos de puertos, muros de permisos, variables de entorno faltantes, errores de YAML, colisiones de Docker, uso de rollback/checkpoint, aciertos de caché o pequeñas correcciones tras mucho texto rojo.",
      +    },
      +    filters: {
      +      all_categories: "Todos",
      +      visibility_all: "todos",
      +      visibility_unlocked: "desbloqueados",
      +      visibility_discovered: "descubiertos",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Compartir logro",
      +      header: "Compartir: {name}",
      +      close: "Cerrar",
      +      rendering: "Renderizando…",
      +      card_alt: "Tarjeta para compartir de {name}",
      +      error_generic: "Algo salió mal.",
      +      x_title: "Abre X con una publicación predefinida",
      +      x_button: "Compartir en X",
      +      copy_title: "Copia la imagen para pegarla en tu publicación",
      +      copy_button: "Copiar imagen",
      +      copied: "Copiado ✓",
      +      download_button: "Descargar PNG",
      +      hint:
      +        "Compartir en X abre una publicación predefinida en una nueva pestaña. Haz clic primero en Copiar imagen si quieres adjuntar la insignia 1200×630: X te permite pegarla directamente en el redactor del tuit. Descargar PNG guarda el archivo para usarlo en cualquier lugar.",
      +      clipboard_unsupported:
      +        "Este navegador no admite copiar imágenes al portapapeles: usa Descargar en su lugar.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Cargando tablero Kanban…",
      +    loadFailed: "Error al cargar el tablero Kanban: ",
      +    loadFailedHint:
      +      "El backend crea automáticamente kanban.db en la primera lectura. Si el problema persiste, revisa los registros del panel.",
      +    board: "Tablero",
      +    newBoard: "+ Nuevo tablero",
      +    newBoardTitle: "Nuevo tablero",
      +    newBoardDescription:
      +      "Los tableros te permiten separar flujos de trabajo no relacionados — uno por proyecto, repositorio o dominio. Los workers de un tablero nunca ven las tareas de otro.",
      +    slug: "Slug",
      +    slugHint: "— minúsculas, guiones, p. ej. atm10-server",
      +    displayName: "Nombre visible",
      +    displayNameHint: "(opcional)",
      +    description: "Descripción",
      +    descriptionHint: "(opcional)",
      +    icon: "Icono",
      +    iconHint: "(un solo carácter o emoji)",
      +    switchAfterCreate: "Cambiar a este tablero tras crearlo",
      +    cancel: "Cancelar",
      +    creating: "Creando…",
      +    createBoard: "Crear tablero",
      +    search: "Buscar",
      +    filterCards: "Filtrar tarjetas…",
      +    tenant: "Tenant",
      +    allTenants: "Todos los tenants",
      +    assignee: "Asignado a",
      +    allProfiles: "Todos los perfiles",
      +    showArchived: "Mostrar archivados",
      +    lanesByProfile: "Carriles por perfil",
      +    nudgeDispatcher: "Avisar al dispatcher",
      +    refresh: "Actualizar",
      +    selected: "seleccionado(s)",
      +    complete: "Completar",
      +    archive: "Archivar",
      +    apply: "Aplicar",
      +    clear: "Limpiar",
      +    createTask: "Crear tarea en esta columna",
      +    noTasks: "— sin tareas —",
      +    unassigned: "sin asignar",
      +    untitled: "(sin título)",
      +    loadingDetail: "Cargando…",
      +    addComment: "Añadir un comentario… (Enter para enviar)",
      +    comment: "Comentario",
      +    status: "Estado",
      +    workspace: "Workspace",
      +    skills: "Habilidades",
      +    createdBy: "Creado por",
      +    result: "Result",
      +    comments: "Comentarios",
      +    events: "Eventos",
      +    runHistory: "Historial de ejecuciones",
      +    workerLog: "Registro del worker",
      +    loadingLog: "Cargando registro…",
      +    noWorkerLog:
      +      "— aún no hay registro del worker (la tarea no se ha lanzado o el registro fue rotado) —",
      +    noDescription: "— sin descripción —",
      +    noComments: "— sin comentarios —",
      +    edit: "editar",
      +    save: "Guardar",
      +    dependencies: "Dependencias",
      +    parents: "Padres:",
      +    children: "Hijos:",
      +    none: "ninguno",
      +    addParent: "— añadir padre —",
      +    addChild: "— añadir hijo —",
      +    removeDependency: "Eliminar dependencia",
      +    block: "Bloquear",
      +    unblock: "Desbloquear",
      +    notifyHomeChannels: "Notificar a los canales de inicio",
      +    diagnostics: "Diagnósticos",
      +    hide: "Ocultar",
      +    show: "Mostrar",
      +    attention: "Atención",
      +    tasksNeedAttention: "tareas requieren atención",
      +    taskNeedsAttention: "1 tarea requiere atención",
      +    diagnostic: "diagnóstico",
      +    open: "Abrir",
      +    close: "Cerrar (Esc)",
      +    reassignTo: "Reasignar a:",
      +    copied: "Copiado",
      +    copyCommand: "Copiar comando al portapapeles",
      +    reclaim: "Recuperar",
      +    reassign: "Reasignar",
      +    renderingError: "La pestaña Kanban tuvo un error de renderizado",
      +    reloadView: "Recargar vista",
      +    wsAuthFailed:
      +      "Error de autenticación de WebSocket — recarga la página para refrescar el token de sesión.",
      +    markDone: "¿Marcar {n} tarea(s) como hechas?",
      +    markArchived: "¿Archivar {n} tarea(s)?",
      +    warning: "Advertencia",
      +    phantomIds: "IDs fantasma:",
      +    active: "activo",
      +    ended: "finalizado",
      +    noProfile: "(sin perfil)",
      +    showAllAttempts: "Mostrar todos los intentos",
      +    sendingUpdates: "Enviando actualizaciones a",
      +    sendNotifications: "Enviar notificaciones de completed / blocked / gave_up a",
      +    archiveBoardConfirm:
      +      "¿Archivar el tablero '{name}'? Se moverá a boards/_archived/ para que puedas recuperarlo más tarde. Las tareas de este tablero ya no aparecerán en ninguna parte de la UI.",
      +    archiveBoardTitle: "Archivar este tablero",
      +    boardSwitcherHint: "Los tableros te permiten separar flujos de trabajo no relacionados",
      +    taskCreatedWarning: "Tarea creada, pero: ",
      +    moveFailed: "Error al mover: ",
      +    bulkFailed: "Lote: ",
      +    completionBlockedHallucination: "⚠ Completado bloqueado — IDs de tarjeta fantasma",
      +    suspectedHallucinatedReferences: "⚠ El texto referenció IDs de tarjeta fantasma",
      +    pickProfileFirst: "Elige primero un perfil.",
      +    unblockedMessage: "Desbloqueado {id}. La tarea está lista para el próximo tick.",
      +    unblockFailed: "Error al desbloquear: ",
      +    reclaimedMessage: "Recuperado {id}. La tarea vuelve a estar lista.",
      +    reclaimFailed: "Error al recuperar: ",
      +    reassignedMessage: "Reasignado {id} a {profile}.",
      +    reassignFailed: "Error al reasignar: ",
      +    selectForBulk: "Seleccionar para acciones por lotes",
      +    clickToEdit: "Haz clic para editar",
      +    clickToEditAssignee: "Haz clic para editar el asignado",
      +    emptyAssignee: "(vacío = sin asignar)",
      +    columnLabels: {
      +      triage: "Clasificación",
      +      todo: "Por hacer",
      +      ready: "Listo",
      +      running: "En curso",
      +      blocked: "Bloqueado",
      +      done: "Hecho",
      +      archived: "Archivado",
      +    },
      +    columnHelp: {
      +      triage: "Ideas en bruto — un specifier desarrollará la especificación",
      +      todo: "Esperando dependencias o sin asignar",
      +      ready: "Asignado y esperando un tick del dispatcher",
      +      running: "Reclamado por un worker — en ejecución",
      +      blocked: "El worker pidió intervención humana",
      +      done: "Completado",
      +      archived: "Archivado",
      +    },
      +    confirmDone:
      +      "¿Marcar esta tarea como hecha? Se libera el reclamo del worker y los hijos dependientes pasan a estar listos.",
      +    confirmArchive:
      +      "¿Archivar esta tarea? Desaparecerá de la vista por defecto del tablero.",
      +    confirmBlocked:
      +      "¿Marcar esta tarea como bloqueada? Se libera el reclamo del worker.",
      +    completionSummary:
      +      "Resumen de finalización para {label}. Se almacena como el result de la tarea.",
      +    completionSummaryRequired:
      +      "El resumen de finalización es obligatorio antes de marcar una tarea como hecha.",
      +    triagePlaceholder: "Idea aproximada — la IA la especificará…",
      +    taskTitlePlaceholder: "Título de la nueva tarea…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "asignado",
      +    priority: "Prioridad",
      +    skillsPlaceholder:
      +      "habilidades (opcional, separadas por comas): translation, github-code-review",
      +    noParent: "— sin padre —",
      +    workspacePathDir: "ruta del workspace (obligatoria, p. ej. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "ruta del workspace (opcional, derivada del asignado si está vacía)",
      +    logTruncated: "(mostrando los últimos 100 KB — registro completo en ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/fr.ts
      @@ -0,0 +1,695 @@
      +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 du basculement",
      +    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 de ce plugin. Vérifiez l'onglet Réseau (dashboard-plugins/…) et le chemin des plugins du serveur.",
      +    pluginNotRegistered:
      +      "Le script du plugin n'a pas appelé register(), ou le script a échoué. Ouvrez la console du navigateur pour plus de détails.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Fermer la navigation",
      +    closeModelTools: "Fermer modèle et 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",
      +      stopped: "Arrêté",
      +    },
      +    nav: {
      +      analytics: "Analyses",
      +      chat: "Chat",
      +      config: "Configuration",
      +      cron: "Cron",
      +      documentation: "Documentation",
      +      keys: "Clés",
      +      logs: "Journaux",
      +      models: "Modèles",
      +      profiles: "profils : multi agents",
      +      plugins: "Plugins",
      +      sessions: "Sessions",
      +      skills: "Compétences",
      +    },
      +    modelToolsSheetSubtitle: "& outils",
      +    modelToolsSheetTitle: "Modèle",
      +    navigation: "Navigation",
      +    openDocumentation: "Ouvrir la documentation dans un nouvel onglet",
      +    openNavigation: "Ouvrir la navigation",
      +    pluginNavSection: "Plugins",
      +    sessionsActiveCount: "{count} actives",
      +    statusOverview: "Aperçu de l'état",
      +    system: "Système",
      +    webUi: "Web UI",
      +  },
      +
      +  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: "Le démarrage de la passerelle a échoué",
      +    lastUpdate: "Dernière mise à jour",
      +    noneRunning: "Aucun",
      +    notRunning: "Non lancé",
      +    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 (distant)",
      +    startFailed: "Échec du démarrage",
      +    starting: "Démarrage",
      +    startedInBackground: "Démarré en arrière-plan — consultez les journaux pour la progression",
      +    stopped: "Arrêté",
      +    updateHermes: "Mettre à jour Hermes",
      +    updatingHermes: "Mise à jour de Hermes…",
      +    waitingForOutput: "En attente de la sortie…",
      +  },
      +
      +  sessions: {
      +    title: "Sessions",
      +    searchPlaceholder: "Rechercher dans les messages...",
      +    noSessions: "Aucune session pour l'instant",
      +    noMatch: "Aucune session ne correspond à votre recherche",
      +    startConversation: "Démarrez une conversation pour la voir ici",
      +    noMessages: "Aucun message",
      +    untitledSession: "Session sans titre",
      +    deleteSession: "Supprimer la session",
      +    confirmDeleteTitle: "Supprimer la session ?",
      +    confirmDeleteMessage:
      +      "Cela supprime 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: "[redacted:secret-assignment] totaux",
      +    totalSessions: "Sessions totales",
      +    apiCalls: "Appels API",
      +    dailyTokenUsage: "[redacted:secret-assignment] quotidienne des tokens",
      +    dailyBreakdown: "Détail quotidien",
      +    perModelBreakdown: "Détail par modèle",
      +    topSkills: "Compétences les plus utilisées",
      +    skill: "Compétence",
      +    loads: "Agent chargé",
      +    edits: "Agent géré",
      +    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: "[redacted:secret-assignment]",
      +    perDayAvg: "/jour moy",
      +    acrossModels: "sur {count} modèles",
      +    inOut: "{input} entrée / {output} sortie",
      +  },
      +
      +  models: {
      +    modelsUsed: "Modèles utilisés",
      +    estimatedCost: "Coût est.",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "sessions",
      +    avgPerSession: "moy/session",
      +    apiCalls: "appels API",
      +    toolCalls: "appels d'outil",
      +    noModelsData: "Aucune donnée de modèle pour cette période",
      +    startSession: "Démarrez une session pour voir les données de modèle ici",
      +  },
      +
      +  logs: {
      +    title: "Journaux",
      +    autoRefresh: "Actualisation auto",
      +    file: "Fichier",
      +    level: "Niveau",
      +    component: "Composant",
      +    lines: "Lignes",
      +    noLogLines: "Aucune ligne de journal trouvée",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Cela supprime la tâche du planning. Cette action est irréversible.",
      +    confirmDeleteTitle: "Supprimer la tâche planifiée ?",
      +    newJob: "Nouvelle tâche cron",
      +    nameOptional: "Nom (facultatif)",
      +    namePlaceholder: "ex. Résumé quotidien",
      +    prompt: "Invite",
      +    promptPlaceholder: "Que doit faire l'agent à chaque exécution ?",
      +    schedule: "Planning (expression cron)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Livrer à",
      +    scheduledJobs: "Tâches planifiées",
      +    noJobs: "Aucune tâche cron configuré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: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Nouveau profil",
      +    name: "Nom",
      +    namePlaceholder: "ex. coder, writer, etc.",
      +    nameRequired: "Le nom est requis",
      +    nameRule:
      +      "Lettres minuscules, chiffres, _ et - uniquement ; doit commencer par une lettre ou un chiffre ; jusqu'à 64 caractères.",
      +    invalidName: "Nom de profil invalide",
      +    cloneFromDefault: "Cloner la configuration du profil par défaut",
      +    allProfiles: "Profils",
      +    noProfiles: "Aucun profil trouvé.",
      +    defaultBadge: "défaut",
      +    hasEnv: "env",
      +    model: "Modèle",
      +    skills: "Compétences",
      +    rename: "Renommer",
      +    editSoul: "Modifier SOUL.md",
      +    soulSection: "SOUL.md (personnalité / invite 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 supprime définitivement le profil '{name}' — configuration, clés, mémoires, sessions, compétences, tâches cron. Action 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 d'abord le dossier existant)",
      +    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 le raccourci owner/repo ou une URL de clonage complète https:// ou git@.",
      +    memoryProviderLabel: "Fournisseur de mémoire",
      +    missingEnvWarn: "Définissez ces variables dans Clés avant que le plugin puisse s'exécuter:",
      +    noDashboardTab: "Aucun onglet de tableau de bord",
      +    openTab: "Ouvrir",
      +    orphanHeading: "Extensions du tableau de bord uniquement (aucune correspondance plugin.yaml d'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: "Re-scanner les extensions du tableau de bord",
      +    removeConfirm: "Retirer ce plugin de ~/.hermes/plugins/ ?",
      +    removeHint: "Seuls les plugins installés par l'utilisateur sous ~/.hermes/plugins peuvent être supprimés.",
      +    rescanHeading: "Registre des plugins SPA",
      +    rescanHint: "Re-scannez après avoir ajouté des fichiers sur le disque pour que la barre latérale prenne en compte les nouveaux manifestes.",
      +    runtimeHeading: "Exécution de la passerelle (plugins YAML)",
      +    saveProviders: "Enregistrer les paramètres de fournisseur",
      +    savedProviders: "Paramètres de fournisseur enregistrés.",
      +    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 outils...",
      +    enabledOf: "{enabled}/{total} activées",
      +    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 ou filtre.",
      +    skillCount: "{count} compétence{s}",
      +    resultCount: "{count} résultat{s}",
      +    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} aux valeurs par défaut ? Cela ne met à jour que le formulaire — les modifications ne sont écrites dans config.yaml qu'après avoir appuyé sur Enregistrer.",
      +    resetScopeToast: "{scope} réinitialisé aux valeurs par défaut — vérifiez et enregistrez pour conserver",
      +    rawYaml: "Configuration YAML brute",
      +    searchResults: "Résultats de recherche",
      +    fields: "champ{s}",
      +    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 pour cette variable sera supprimée de votre fichier .env. Cette action ne peut pas être annulée depuis l'interface.",
      +    confirmClearTitle: "Effacer cette clé ?",
      +    description: "Gérer les clés API et les secrets stockés dans",
      +    hideAdvanced: "Masquer les options avancées",
      +    showAdvanced: "Afficher les options avancées",
      +    llmProviders: "Fournisseurs LLM",
      +    providersConfigured: "{configured} sur {total} fournisseurs configurés",
      +    getKey: "Obtenir la clé",
      +    notConfigured: "{count} non configuré",
      +    notSet: "Non défini",
      +    keysCount: "{count} clé{s}",
      +    enterValue: "Saisir 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 le 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: "Connexion",
      +    disconnect: "Déconnecter",
      +    managedExternally: "Géré en externe",
      +    copied: "Copié ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Copier la commande CLI (pour externe / repli)",
      +    connect: "Connecter",
      +    sessionExpires: "La session expire dans {time}",
      +    initiatingLogin: "Lancement du flux de connexion…",
      +    exchangingCode: "Échange du code contre des jetons…",
      +    connectedClosing: "Connecté ! Fermeture…",
      +    loginFailed: "Échec de la connexion.",
      +    sessionExpired: "Session expirée. Cliquez sur Réessayer pour démarrer une nouvelle connexion.",
      +    reOpenAuth: "Rouvrir la page d'authentification",
      +    reOpenVerification: "Rouvrir la page de vérification",
      +    submitCode: "Soumettre le code",
      +    pasteCode: "Collez le code d'autorisation (avec suffixe #state accepté)",
      +    waitingAuth: "En attente de votre autorisation dans le navigateur…",
      +    enterCodePrompt: "Un nouvel onglet s'est ouvert. Saisissez ce code si demandé:",
      +    pkceStep1: "Un nouvel onglet s'est ouvert vers claude.ai. Connectez-vous et cliquez sur Autoriser.",
      +    pkceStep2: "Copiez le code d'autorisation affiché après autorisation.",
      +    pkceStep3: "Collez-le ci-dessous et soumettez.",
      +    flowLabels: {
      +      pkce: "Connexion navigateur (PKCE)",
      +      device_code: "Code d'appareil",
      +      external: "CLI externe",
      +    },
      +    expiresIn: "expire dans {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Passer à l'anglais",
      +  },
      +
      +  theme: {
      +    title: "Thème",
      +    switchTheme: "Changer de thème",
      +  },
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Badges Hermes à collectionner, gagnés à partir de l'historique réel des sessions. Les succès connus non terminés sont affichés comme Découverts ; les succès secrets restent cachés jusqu'à l'apparition du premier comportement correspondant.",
      +      scan_subtitle:
      +        "Analyse de l'historique des sessions Hermes en cours. Le premier scan peut prendre 5 à 10 secondes sur les historiques volumineux.",
      +    },
      +    actions: {
      +      rescan: "Relancer le scan",
      +    },
      +    stats: {
      +      unlocked: "Débloqués",
      +      unlocked_hint: "badges obtenus",
      +      discovered: "Découverts",
      +      discovered_hint: "connus, pas encore obtenus",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] jusqu'au premier signal",
      +      highest_tier: "Niveau le plus élevé",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Dernier",
      +      latest_hint_empty: "utilisez Hermes davantage",
      +      none_yet: "Aucun pour l'instant",
      +    },
      +    state: {
      +      unlocked: "Débloqué",
      +      discovered: "Découvert",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Cible {tier}",
      +      hidden: "Caché",
      +      complete: "Terminé",
      +      objective: "Objectif",
      +    },
      +    progress: {
      +      hidden: "caché",
      +    },
      +    scan: {
      +      building_headline: "Création du profil de succès…",
      +      building_detail:
      +        "Lecture des sessions, des appels d'outils, des métadonnées du modèle et de l'état de déblocage.",
      +      starting_headline: "Démarrage du scan des succès…",
      +      progress_detail:
      +        "{scanned} sessions analysées sur {total} · {pct}%. Les badges se débloquent à mesure que l'historique est traité.",
      +      idle_detail:
      +        "Lecture des sessions, des appels d'outils, des métadonnées du modèle et de l'état de déblocage. Les badges apparaissent ici à mesure qu'ils se débloquent.",
      +    },
      +    guide: {
      +      tiers_header: "Niveaux",
      +      secret_header: "[redacted:secret-assignment] secrets",
      +      secret_body:
      +        "Les secrets cachent leur déclencheur exact. Dès qu'Hermes détecte un signal lié, la carte passe à Découvert et affiche son exigence.",
      +      scan_status_header: "État du scan",
      +      scan_status_body:
      +        "Hermes analyse l'historique local une seule fois, puis les cartes apparaîtront automatiquement. Rien n'est bloqué si cela prend quelques secondes.",
      +      what_scanned_header: "Ce qui est analysé",
      +      what_scanned_body:
      +        "Sessions, appels d'outils, métadonnées du modèle, erreurs, succès et état de déblocage local.",
      +    },
      +    card: {
      +      share_title: "Partager ce succès",
      +      share_label: "Partager {name}",
      +      share_text: "Partager",
      +      how_to_reveal: "Comment le révéler",
      +      what_counts: "Ce qui compte",
      +      evidence_label: "Preuve",
      +      evidence_session_fallback: "session",
      +      no_evidence: "Pas encore de preuve",
      +    },
      +    latest: {
      +      header: "Déblocages récents",
      +    },
      +    empty: {
      +      no_secrets_header: "Plus aucun secret caché dans ce scan.",
      +      no_secrets_body:
      +        "Indice: les secrets démarrent généralement à partir d'échecs inhabituels ou de schémas d'utilisateurs avancés — conflits de ports, murs de permissions, variables d'environnement manquantes, erreurs YAML, collisions Docker, utilisation de rollback/checkpoint, succès de cache ou petits correctifs après beaucoup de texte rouge.",
      +    },
      +    filters: {
      +      all_categories: "Tous",
      +      visibility_all: "tous",
      +      visibility_unlocked: "débloqués",
      +      visibility_discovered: "découverts",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Partager le succès",
      +      header: "Partager: {name}",
      +      close: "Fermer",
      +      rendering: "Rendu en cours…",
      +      card_alt: "Carte de partage {name}",
      +      error_generic: "Une erreur s'est produite.",
      +      x_title: "Ouvre X avec une publication préremplie",
      +      x_button: "Partager sur X",
      +      copy_title: "Copiez l'image pour la coller dans votre publication",
      +      copy_button: "Copier l'image",
      +      copied: "Copié ✓",
      +      download_button: "Télécharger le PNG",
      +      hint:
      +        "Partager sur X ouvre une publication préremplie dans un nouvel onglet. Cliquez d'abord sur Copier l'image si vous voulez joindre le badge 1200×630 — X vous laisse le coller directement dans l'éditeur de tweet. Télécharger le PNG enregistre le fichier pour l'utiliser n'importe où.",
      +      clipboard_unsupported:
      +        "La copie d'image dans le presse-papiers n'est pas prise en charge par ce navigateur — utilisez Télécharger à la place.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Chargement du tableau Kanban…",
      +    loadFailed: "Échec du chargement du tableau Kanban: ",
      +    loadFailedHint:
      +      "Le backend crée automatiquement kanban.db à la première lecture. Si le problème persiste, consultez les logs du dashboard.",
      +    board: "Tableau",
      +    newBoard: "+ Nouveau tableau",
      +    newBoardTitle: "Nouveau tableau",
      +    newBoardDescription:
      +      "Les tableaux vous permettent de séparer des flux de travail indépendants — un par projet, dépôt ou domaine. Les workers d'un tableau ne voient jamais les tâches d'un autre.",
      +    slug: "Slug",
      +    slugHint: "— minuscules, tirets, par ex. atm10-server",
      +    displayName: "Nom affiché",
      +    displayNameHint: "(facultatif)",
      +    description: "Description",
      +    descriptionHint: "(facultatif)",
      +    icon: "Icône",
      +    iconHint: "(un seul caractère ou emoji)",
      +    switchAfterCreate: "Basculer sur ce tableau après l'avoir créé",
      +    cancel: "Annuler",
      +    creating: "Création…",
      +    createBoard: "Créer le tableau",
      +    search: "Rechercher",
      +    filterCards: "Filtrer les cartes…",
      +    tenant: "Tenant",
      +    allTenants: "Tous les tenants",
      +    assignee: "Assigné à",
      +    allProfiles: "Tous les profils",
      +    showArchived: "Afficher les archivés",
      +    lanesByProfile: "Couloirs par profil",
      +    nudgeDispatcher: "Solliciter le dispatcher",
      +    refresh: "Rafraîchir",
      +    selected: "sélectionné(s)",
      +    complete: "Terminer",
      +    archive: "Archiver",
      +    apply: "Appliquer",
      +    clear: "Effacer",
      +    createTask: "Créer une tâche dans cette colonne",
      +    noTasks: "— aucune tâche —",
      +    unassigned: "non assigné",
      +    untitled: "(sans titre)",
      +    loadingDetail: "Chargement…",
      +    addComment: "Ajouter un commentaire… (Enter pour envoyer)",
      +    comment: "Commentaire",
      +    status: "Statut",
      +    workspace: "Workspace",
      +    skills: "Compétences",
      +    createdBy: "Créé par",
      +    result: "Result",
      +    comments: "Commentaires",
      +    events: "Événements",
      +    runHistory: "Historique d'exécution",
      +    workerLog: "Log du worker",
      +    loadingLog: "Chargement du log…",
      +    noWorkerLog:
      +      "— pas encore de log du worker (la tâche n'a pas démarré ou le log a été effacé par rotation) —",
      +    noDescription: "— aucune description —",
      +    noComments: "— aucun commentaire —",
      +    edit: "modifier",
      +    save: "Enregistrer",
      +    dependencies: "Dépendances",
      +    parents: "Parents:",
      +    children: "Enfants:",
      +    none: "aucun",
      +    addParent: "— ajouter un parent —",
      +    addChild: "— ajouter un enfant —",
      +    removeDependency: "Supprimer la dépendance",
      +    block: "Bloquer",
      +    unblock: "Débloquer",
      +    notifyHomeChannels: "Notifier les canaux home",
      +    diagnostics: "Diagnostics",
      +    hide: "Masquer",
      +    show: "Afficher",
      +    attention: "Attention",
      +    tasksNeedAttention: "tâches nécessitent une attention",
      +    taskNeedsAttention: "1 tâche nécessite une attention",
      +    diagnostic: "diagnostic",
      +    open: "Ouvrir",
      +    close: "Fermer (Esc)",
      +    reassignTo: "Réassigner à:",
      +    copied: "Copié",
      +    copyCommand: "Copier la commande dans le presse-papiers",
      +    reclaim: "Récupérer",
      +    reassign: "Réassigner",
      +    renderingError: "L'onglet Kanban a rencontré une erreur de rendu",
      +    reloadView: "Recharger la vue",
      +    wsAuthFailed:
      +      "Échec d'authentification WebSocket — rechargez la page pour rafraîchir le jeton de session.",
      +    markDone: "Marquer {n} tâche(s) comme terminée(s) ?",
      +    markArchived: "Archiver {n} tâche(s) ?",
      +    warning: "Avertissement",
      +    phantomIds: "IDs fantômes:",
      +    active: "actif",
      +    ended: "terminé",
      +    noProfile: "(aucun profil)",
      +    showAllAttempts: "Afficher toutes les tentatives",
      +    sendingUpdates: "Envoi des mises à jour à",
      +    sendNotifications: "Envoyer les notifications completed / blocked / gave_up à",
      +    archiveBoardConfirm:
      +      "Archiver le tableau '{name}' ? Il sera déplacé vers boards/_archived/ pour pouvoir être récupéré plus tard. Les tâches de ce tableau n'apparaîtront plus nulle part dans l'UI.",
      +    archiveBoardTitle: "Archiver ce tableau",
      +    boardSwitcherHint: "Les tableaux vous permettent de séparer des flux de travail indépendants",
      +    taskCreatedWarning: "Tâche créée, mais: ",
      +    moveFailed: "Échec du déplacement: ",
      +    bulkFailed: "Lot: ",
      +    completionBlockedHallucination: "⚠ Achèvement bloqué — IDs de carte fantômes",
      +    suspectedHallucinatedReferences: "⚠ Le texte a référencé des IDs de carte fantômes",
      +    pickProfileFirst: "Choisissez d'abord un profil.",
      +    unblockedMessage: "Débloqué {id}. La tâche est prête pour le prochain tick.",
      +    unblockFailed: "Échec du déblocage: ",
      +    reclaimedMessage: "Récupéré {id}. La tâche est de nouveau prête.",
      +    reclaimFailed: "Échec de la récupération: ",
      +    reassignedMessage: "Réassigné {id} à {profile}.",
      +    reassignFailed: "Échec de la réassignation: ",
      +    selectForBulk: "Sélectionner pour des actions groupées",
      +    clickToEdit: "Cliquez pour modifier",
      +    clickToEditAssignee: "Cliquez pour modifier l'assigné",
      +    emptyAssignee: "(vide = désassigner)",
      +    columnLabels: {
      +      triage: "Triage",
      +      todo: "À faire",
      +      ready: "Prêt",
      +      running: "En cours",
      +      blocked: "Bloqué",
      +      done: "Terminé",
      +      archived: "Archivé",
      +    },
      +    columnHelp: {
      +      triage: "Idées brutes — un specifier rédigera la spécification",
      +      todo: "En attente de dépendances ou non assigné",
      +      ready: "Assigné et en attente d'un tick du dispatcher",
      +      running: "Réclamé par un worker — en cours d'exécution",
      +      blocked: "Le worker a demandé une intervention humaine",
      +      done: "Terminé",
      +      archived: "Archivé",
      +    },
      +    confirmDone:
      +      "Marquer cette tâche comme terminée ? La revendication du worker est libérée et les enfants dépendants deviennent prêts.",
      +    confirmArchive:
      +      "Archiver cette tâche ? Elle disparaîtra de la vue par défaut du tableau.",
      +    confirmBlocked:
      +      "Marquer cette tâche comme bloquée ? La revendication du worker est libérée.",
      +    completionSummary:
      +      "Résumé d'achèvement pour {label}. Stocké comme result de la tâche.",
      +    completionSummaryRequired:
      +      "Un résumé d'achèvement est requis avant de marquer une tâche comme terminée.",
      +    triagePlaceholder: "Idée approximative — l'IA la spécifiera…",
      +    taskTitlePlaceholder: "Titre de la nouvelle tâche…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "assigné",
      +    priority: "Priorité",
      +    skillsPlaceholder:
      +      "compétences (facultatif, séparées par virgules): translation, github-code-review",
      +    noParent: "— aucun parent —",
      +    workspacePathDir: "chemin du workspace (requis, par ex. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "chemin du workspace (facultatif, dérivé de l'assigné si vide)",
      +    logTruncated: "(affichage des derniers 100 KB — log complet à ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/ga.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const ga: Translations = {
      +  common: {
      +    save: "Sábháil",
      +    saving: "Á shábháil...",
      +    cancel: "Cealaigh",
      +    close: "Dún",
      +    confirm: "Deimhnigh",
      +    delete: "Scrios",
      +    refresh: "Athnuaigh",
      +    retry: "Bain triail eile as",
      +    search: "Cuardaigh...",
      +    loading: "Á luchtú...",
      +    create: "Cruthaigh",
      +    creating: "Á chruthú...",
      +    set: "Socraigh",
      +    replace: "Athchuir",
      +    clear: "Glan",
      +    live: "Beo",
      +    off: "As",
      +    enabled: "cumasaithe",
      +    disabled: "díchumasaithe",
      +    active: "gníomhach",
      +    inactive: "neamhghníomhach",
      +    unknown: "anaithnid",
      +    untitled: "Gan teideal",
      +    none: "Aon cheann",
      +    form: "Foirm",
      +    noResults: "Aon toradh",
      +    of: "as",
      +    page: "Leathanach",
      +    msgs: "tcht",
      +    tools: "uirlisí",
      +    match: "meaitseáil",
      +    other: "Eile",
      +    configured: "cumraithe",
      +    removed: "bainte",
      +    failedToToggle: "Theip ar an scoránú",
      +    failedToRemove: "Theip ar an mbaint",
      +    failedToReveal: "Theip ar an taispeáint",
      +    collapse: "Laghdaigh",
      +    expand: "Leathnaigh",
      +    general: "Ginearálta",
      +    messaging: "Teachtaireachtaí",
      +    pluginLoadFailed:
      +      "Níorbh fhéidir script an plugin seo a luchtú. Seiceáil an cluaisín Network (dashboard-plugins/…) agus conair plugin an fhreastalaí.",
      +    pluginNotRegistered:
      +      "Níor ghlaoigh script an plugin ar register(), nó tharla earráid sa script. Oscail consól an bhrabhsálaí le haghaidh sonraí.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Dún an nascleanúint",
      +    closeModelTools: "Dún an samhail agus na huirlisí",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Seisiúin gníomhacha:",
      +    gatewayStatusLabel: "Stádas an gateway:",
      +    gatewayStrip: {
      +      failed: "Theip ar an tús",
      +      off: "As",
      +      running: "Ag rith",
      +      starting: "Ag tosú",
      +      stopped: "Stoptha",
      +    },
      +    nav: {
      +      analytics: "Anailís",
      +      chat: "Comhrá",
      +      config: "Cumraíocht",
      +      cron: "Cron",
      +      documentation: "Doiciméadú",
      +      keys: "Eochracha",
      +      logs: "Logaí",
      +      models: "Samhlacha",
      +      profiles: "próifílí : il-agents",
      +      plugins: "Plugins",
      +      sessions: "Seisiúin",
      +      skills: "Scileanna",
      +    },
      +    modelToolsSheetSubtitle: "agus uirlisí",
      +    modelToolsSheetTitle: "Samhail",
      +    navigation: "Nascleanúint",
      +    openDocumentation: "Oscail an doiciméadú i gcluaisín nua",
      +    openNavigation: "Oscail an nascleanúint",
      +    pluginNavSection: "Plugins",
      +    sessionsActiveCount: "{count} gníomhach",
      +    statusOverview: "Forbhreathnú stádais",
      +    system: "Córas",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Theip ar an ngníomh",
      +    actionFinished: "Críochnaithe",
      +    actions: "Gníomhartha",
      +    agent: "Agent",
      +    activeSessions: "Seisiúin ghníomhacha",
      +    connected: "Ceangailte",
      +    connectedPlatforms: "Ardáin cheangailte",
      +    disconnected: "Dícheangailte",
      +    error: "Earráid",
      +    failed: "Theip",
      +    gateway: "Gateway",
      +    gatewayFailedToStart: "Theip ar an gateway tosú",
      +    lastUpdate: "Nuashonrú deireanach",
      +    noneRunning: "Aon cheann",
      +    notRunning: "Níl ag rith",
      +    pid: "PID",
      +    platformDisconnected: "dícheangailte",
      +    platformError: "earráid",
      +    recentSessions: "Seisiúin le déanaí",
      +    restartGateway: "Atosaigh an gateway",
      +    restartingGateway: "Ag atosú an gateway…",
      +    running: "Ag rith",
      +    runningRemote: "Ag rith (cianda)",
      +    startFailed: "Theip ar an tús",
      +    starting: "Ag tosú",
      +    startedInBackground: "Tosaithe sa chúlra — seiceáil na logaí le haghaidh dul chun cinn",
      +    stopped: "Stoptha",
      +    updateHermes: "Nuashonraigh Hermes",
      +    updatingHermes: "Ag nuashonrú Hermes…",
      +    waitingForOutput: "Ag fanacht le haschur…",
      +  },
      +
      +  sessions: {
      +    title: "Seisiúin",
      +    searchPlaceholder: "Cuardaigh ábhar teachtaireachta...",
      +    noSessions: "Gan seisiúin go fóill",
      +    noMatch: "Níl seisiún ar bith ag teacht le do chuardach",
      +    startConversation: "Tosaigh comhrá chun é a fheiceáil anseo",
      +    noMessages: "Gan teachtaireachtaí",
      +    untitledSession: "Seisiún gan teideal",
      +    deleteSession: "Scrios an seisiún",
      +    confirmDeleteTitle: "Scrios an seisiún?",
      +    confirmDeleteMessage:
      +      "Baineann sé seo an comhrá agus a chuid teachtaireachtaí ar fad go buan. Ní féidir é seo a chealú.",
      +    sessionDeleted: "Seisiún scriosta",
      +    failedToDelete: "Theip ar scriosadh an tseisiúin",
      +    resumeInChat: "Lean ar aghaidh sa chomhrá",
      +    previousPage: "Leathanach roimhe seo",
      +    nextPage: "An chéad leathanach eile",
      +    roles: {
      +      user: "Úsáideoir",
      +      assistant: "Cúntóir",
      +      system: "Córas",
      +      tool: "Uirlis",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Tréimhse:",
      +    totalTokens: "[redacted:secret-assignment] iomlána",
      +    totalSessions: "Seisiúin iomlána",
      +    apiCalls: "Glaonna API",
      +    dailyTokenUsage: "Úsáid laethúil tokens",
      +    dailyBreakdown: "Miondealú laethúil",
      +    perModelBreakdown: "Miondealú de réir samhla",
      +    topSkills: "Príomhscileanna",
      +    skill: "Scil",
      +    loads: "Luchtaithe ag an Agent",
      +    edits: "Bainistithe ag an Agent",
      +    lastUsed: "Úsáidte go deireanach",
      +    input: "Ionchur",
      +    output: "Aschur",
      +    total: "Iomlán",
      +    noUsageData: "Gan sonraí úsáide don tréimhse seo",
      +    startSession: "Tosaigh seisiún chun anailís a fheiceáil anseo",
      +    date: "Dáta",
      +    model: "Samhail",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/lá meán",
      +    acrossModels: "thar {count} samhail",
      +    inOut: "{input} isteach / {output} amach",
      +  },
      +
      +  models: {
      +    modelsUsed: "Samhlacha úsáidte",
      +    estimatedCost: "Costas measta",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "seisiúin",
      +    avgPerSession: "meán/seisiún",
      +    apiCalls: "glaonna API",
      +    toolCalls: "glaonna uirlise",
      +    noModelsData: "Gan sonraí úsáide samhla don tréimhse seo",
      +    startSession: "Tosaigh seisiún chun sonraí samhla a fheiceáil anseo",
      +  },
      +
      +  logs: {
      +    title: "Logaí",
      +    autoRefresh: "Athnuachan uathoibríoch",
      +    file: "Comhad",
      +    level: "Leibhéal",
      +    component: "Comhpháirt",
      +    lines: "Línte",
      +    noLogLines: "Níor aimsíodh línte loga",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Baineann sé seo an post ón sceideal. Ní féidir é seo a chealú.",
      +    confirmDeleteTitle: "Scrios an post sceidealta?",
      +    newJob: "Post Cron Nua",
      +    nameOptional: "Ainm (roghnach)",
      +    namePlaceholder: "m.sh. Achoimre laethúil",
      +    prompt: "Prompt",
      +    promptPlaceholder: "Cad ba chóir don agent a dhéanamh ag gach rith?",
      +    schedule: "Sceideal (slonn cron)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Seachadadh chuig",
      +    scheduledJobs: "Poist sceidealta",
      +    noJobs: "Níl poist cron cumraithe. Cruthaigh ceann thuas.",
      +    last: "Deireanach",
      +    next: "Ar aghaidh",
      +    pause: "Sos",
      +    resume: "Lean ar aghaidh",
      +    triggerNow: "Spreag anois",
      +    delivery: {
      +      local: "Áitiúil",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Próifíl Nua",
      +    name: "Ainm",
      +    namePlaceholder: "m.sh. coder, writer, srl.",
      +    nameRequired: "Tá ainm riachtanach",
      +    nameRule:
      +      "Litreacha cás íochtair, digití, _ agus - amháin; caithfidh tús a chur le litir nó digit; suas le 64 carachtar.",
      +    invalidName: "Ainm próifíle neamhbhailí",
      +    cloneFromDefault: "Clónáil cumraíocht ón bpróifíl réamhshocraithe",
      +    allProfiles: "Próifílí",
      +    noProfiles: "Níor aimsíodh próifílí.",
      +    defaultBadge: "réamhshocraithe",
      +    hasEnv: "env",
      +    model: "Samhail",
      +    skills: "Scileanna",
      +    rename: "Athainmnigh",
      +    editSoul: "Cuir SOUL.md in eagar",
      +    soulSection: "SOUL.md (pearsantacht / prompt córais)",
      +    soulPlaceholder: "# Conas ba chóir don agent seo iompar…",
      +    saveSoul: "Sábháil SOUL",
      +    soulSaved: "SOUL.md sábháilte",
      +    openInTerminal: "Cóipeáil ordú CLI",
      +    commandCopied: "Cóipeáilte chuig an ngearrthaisce",
      +    copyFailed: "Níorbh fhéidir cóipeáil",
      +    confirmDeleteTitle: "Scrios an phróifíl?",
      +    confirmDeleteMessage:
      +      "Scriosann sé seo an phróifíl '{name}' go buan — cumraíocht, eochracha, cuimhní, seisiúin, scileanna, poist cron. Ní féidir é a chealú.",
      +    created: "Cruthaithe",
      +    deleted: "Scriosta",
      +    renamed: "Athainmnithe",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Inneall comhthéacs",
      +    dashboardSlots: "Slots an dashboard",
      +    disableRuntime: "Díchumasaigh",
      +    enableAfterInstall: "Cumasaigh tar éis suiteála",
      +    enableRuntime: "Cumasaigh",
      +    forceReinstall: "Cuir iallach ar athshuiteáil (scrios an fillteán atá ann ar dtús)",
      +    headline:
      +      "Faigh, suiteáil, cumasaigh agus nuashonraigh plugins Hermes (paireacht le `hermes plugins`).",
      +    identifierLabel: "URL Git nó owner/repo",
      +    inactive: "neamhghníomhach",
      +    installBtn: "Suiteáil ó Git",
      +    installHeading: "Suiteáil ó GitHub / URL Git",
      +    installHint: "Úsáid an gearrshamhail owner/repo nó URL clóin iomlán https:// nó git@.",
      +    memoryProviderLabel: "Soláthraí cuimhne",
      +    missingEnvWarn: "Socraigh iad seo in Eochracha sular féidir leis an plugin rith:",
      +    noDashboardTab: "Gan cluaisín dashboard",
      +    openTab: "Oscail",
      +    orphanHeading: "Síntí dashboard amháin (gan meaitseáil le agent plugin.yaml)",
      +    pluginListHeading: "Plugins suiteáilte",
      +    providerDefaults: "ionsuite / réamhshocraithe",
      +    providersHeading: "Plugins soláthraí runtime",
      +    providersHint:
      +      "Scríobhann memory.provider (folamh = ionsuite) agus context.engine chuig config.yaml. Beidh éifeacht aige sa chéad seisiún eile.",
      +    refreshDashboard: "Athscan síntí an dashboard",
      +    removeConfirm: "Bain an plugin seo ó ~/.hermes/plugins/?",
      +    removeHint: "Ní féidir ach plugins atá suiteáilte ag an úsáideoir faoi ~/.hermes/plugins a bhaint.",
      +    rescanHeading: "Clár plugin SPA",
      +    rescanHint: "Athscan tar éis comhaid a chur leis an diosca ionas go n-aimseoidh barra taoibh an dashboard manifests nua.",
      +    runtimeHeading: "Runtime gateway (plugins YAML)",
      +    saveProviders: "Sábháil socruithe an tsoláthraí",
      +    savedProviders: "Socruithe an tsoláthraí sábháilte.",
      +    sourceBadge: "Foinse",
      +    authRequired: "Fíordheimhniú riachtanach",
      +    authRequiredHint: "Rith an t-ordú seo chun fíordheimhniú a dhéanamh:",
      +    updateGit: "Git pull",
      +    versionBadge: "Leagan",
      +    showInSidebar: "Taispeáin sa bharra taoibh",
      +    hideFromSidebar: "Folaigh ón mbarra taoibh",
      +  },
      +
      +  skills: {
      +    title: "Scileanna",
      +    searchPlaceholder: "Cuardaigh scileanna agus toolsets...",
      +    enabledOf: "{enabled}/{total} cumasaithe",
      +    all: "Gach ceann",
      +    categories: "Catagóirí",
      +    filters: "Scagairí",
      +    noSkills: "Níor aimsíodh scileanna. Luchtaítear scileanna ó ~/.hermes/skills/",
      +    noSkillsMatch: "Níl scil ar bith ag teacht le do chuardach nó scagaire.",
      +    skillCount: "{count} scil{s}",
      +    resultCount: "{count} torad{s}",
      +    noDescription: "Gan cur síos ar fáil.",
      +    toolsets: "Toolsets",
      +    toolsetLabel: "toolset {name}",
      +    noToolsetsMatch: "Níl toolset ar bith ag teacht leis an gcuardach.",
      +    setupNeeded: "Socrú ag teastáil",
      +    disabledForCli: "Díchumasaithe don CLI",
      +    more: "+{count} eile",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Scagairí",
      +    sections: "Ranna",
      +    exportConfig: "Easpórtáil cumraíocht mar JSON",
      +    importConfig: "Iompórtáil cumraíocht ó JSON",
      +    resetDefaults: "Athshocraigh chuig réamhshocruithe",
      +    resetScopeTooltip: "Athshocraigh {scope} chuig réamhshocruithe",
      +    confirmResetScope: "Athshocraigh socruithe uile {scope} chuig a réamhshocruithe? Nuashonraíonn sé seo an fhoirm amháin — ní scríobhfar athruithe chuig config.yaml go dtí go mbrúnn tú Sábháil.",
      +    resetScopeToast: "{scope} athshocraithe chuig réamhshocruithe — athbhreithnigh agus Sábháil chun é a choinneáil",
      +    rawYaml: "Cumraíocht YAML amh",
      +    searchResults: "Torthaí cuardaigh",
      +    fields: "réims{s}",
      +    noFieldsMatch: 'Níl aon réimsí ag teacht le "{query}"',
      +    configSaved: "Cumraíocht sábháilte",
      +    yamlConfigSaved: "Cumraíocht YAML sábháilte",
      +    failedToSave: "Theip ar shábháil",
      +    failedToSaveYaml: "Theip ar shábháil an YAML",
      +    failedToLoadRaw: "Theip ar luchtú na cumraíochta amh",
      +    configImported: "Cumraíocht iompórtáilte — athbhreithnigh agus sábháil",
      +    invalidJson: "Comhad JSON neamhbhailí",
      +    categories: {
      +      general: "Ginearálta",
      +      agent: "Agent",
      +      terminal: "Teirminéal",
      +      display: "Taispeáint",
      +      delegation: "Tarmligean",
      +      memory: "Cuimhne",
      +      compression: "Comhbhrú",
      +      security: "Slándáil",
      +      browser: "Brabhsálaí",
      +      voice: "Guth",
      +      tts: "Téacs go Caint",
      +      stt: "Caint go Téacs",
      +      logging: "Logáil",
      +      discord: "Discord",
      +      auxiliary: "Cúntach",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "Sábháiltear athruithe chuig an diosca láithreach. Aimsíonn seisiúin ghníomhacha eochracha nua go huathoibríoch.",
      +    confirmClearMessage:
      +      "Bainfear an luach stóráilte don athróg seo ó do chomhad .env. Ní féidir é seo a chealú ón UI.",
      +    confirmClearTitle: "Glan an eochair seo?",
      +    description: "Bainistigh eochracha API agus rúin atá stóráilte i",
      +    hideAdvanced: "Folaigh Ardroghanna",
      +    showAdvanced: "Taispeáin Ardroghanna",
      +    llmProviders: "Soláthraithe LLM",
      +    providersConfigured: "{configured} as {total} soláthraí cumraithe",
      +    getKey: "Faigh eochair",
      +    notConfigured: "{count} gan cumrú",
      +    notSet: "Gan socrú",
      +    keysCount: "{count} eochai{s}",
      +    enterValue: "Cuir luach isteach...",
      +    replaceCurrentValue: "Athchuir an luach reatha ({preview})",
      +    showValue: "Taispeáin an fíorluach",
      +    hideValue: "Folaigh an luach",
      +  },
      +
      +  oauth: {
      +    title: "Logálacha isteach soláthraí (OAuth)",
      +    providerLogins: "Logálacha isteach soláthraí (OAuth)",
      +    description: "{connected} as {total} soláthraí OAuth ceangailte. Reáchtáiltear sreabha logála isteach faoi láthair tríd an CLI; cliceáil Cóipeáil ordú agus greamaigh i dteirminéal chun é a shocrú.",
      +    connected: "Ceangailte",
      +    expired: "As feidhm",
      +    notConnected: "Gan cheangal. Rith {command} i dteirminéal.",
      +    runInTerminal: "i dteirminéal.",
      +    noProviders: "Níor aimsíodh soláthraithe a thacaíonn le OAuth.",
      +    login: "Logáil isteach",
      +    disconnect: "Dícheangail",
      +    managedExternally: "Bainistithe go seachtrach",
      +    copied: "Cóipeáilte ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Cóipeáil ordú CLI (le haghaidh úsáide seachtraí / cúltaca)",
      +    connect: "Ceangail",
      +    sessionExpires: "Téann an seisiún as feidhm i {time}",
      +    initiatingLogin: "Ag tosú an tsreabha logála isteach…",
      +    exchangingCode: "Ag malartú an chóid ar tokens…",
      +    connectedClosing: "Ceangailte! Á dhúnadh…",
      +    loginFailed: "Theip ar an logáil isteach.",
      +    sessionExpired: "Seisiún as feidhm. Cliceáil Bain triail eile as chun logáil isteach nua a thosú.",
      +    reOpenAuth: "Athoscail an leathanach údaraithe",
      +    reOpenVerification: "Athoscail an leathanach fíoraithe",
      +    submitCode: "Cuir an cód isteach",
      +    pasteCode: "Greamaigh an cód údaraithe (tá iarmhír #state ceart go leor)",
      +    waitingAuth: "Ag fanacht leat údarú a dhéanamh sa bhrabhsálaí…",
      +    enterCodePrompt: "D'oscail cluaisín nua. Cuir an cód seo isteach má iarrtar ort:",
      +    pkceStep1: "D'oscail cluaisín nua chuig claude.ai. Logáil isteach agus cliceáil Údaraigh.",
      +    pkceStep2: "Cóipeáil an cód údaraithe a thaispeántar tar éis údaraithe.",
      +    pkceStep3: "Greamaigh thíos é agus cuir isteach é.",
      +    flowLabels: {
      +      pkce: "Logáil isteach brabhsálaí (PKCE)",
      +      device_code: "Cód gléis",
      +      external: "CLI seachtrach",
      +    },
      +    expiresIn: "as feidhm i {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Athraigh go Béarla",
      +  },
      +
      +  theme: {
      +    title: "Téama",
      +    switchTheme: "Athraigh téama",
      +  },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Suaitheantais Hermes inbhailithe a thuilltear ó stair fíor-session. Léirítear gnóthachtálacha aitheanta neamhchríochnaithe mar Discovered; fanann gnóthachtálacha Secret i bhfolach go dtí go bhfeictear an chéad iompar comhoiriúnach.",
      +      scan_subtitle:
      +        "Stair session Hermes á scanadh. Is féidir leis an gcéad scan 5–10 soicind a thógáil ar staireanna móra.",
      +    },
      +    actions: {
      +      rescan: "Athscan",
      +    },
      +    stats: {
      +      unlocked: "Díghlasáilte",
      +      unlocked_hint: "suaitheantais tuillte",
      +      discovered: "Aimsithe",
      +      discovered_hint: "ar eolas, gan tuilleamh fós",
      +      secrets: "Rúin",
      +      secrets_hint: "i bhfolach go dtí an chéad chomhartha",
      +      highest_tier: "An leibhéal is airde",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "An ceann is déanaí",
      +      latest_hint_empty: "rith Hermes níos mó",
      +      none_yet: "Aon cheann fós",
      +    },
      +    state: {
      +      unlocked: "Díghlasáilte",
      +      discovered: "Aimsithe",
      +      secret: "Rún",
      +    },
      +    tier: {
      +      target: "Sprioc {tier}",
      +      hidden: "I bhfolach",
      +      complete: "Críochnaithe",
      +      objective: "Cuspóir",
      +    },
      +    progress: {
      +      hidden: "i bhfolach",
      +    },
      +    scan: {
      +      building_headline: "Próifíl ghnóthachtála á tógáil…",
      +      building_detail:
      +        "Sessions, glaonna ar uirlisí, meiteashonraí samhla agus staid díghlasála á léamh.",
      +      starting_headline: "Scan ghnóthachtála á thosú…",
      +      progress_detail:
      +        "{scanned} as {total} session scanta · {pct}%. Díghlasáiltear suaitheantais de réir mar a shníonn níos mó staire isteach.",
      +      idle_detail:
      +        "Sessions, glaonna ar uirlisí, meiteashonraí samhla agus staid díghlasála á léamh. Feicfear suaitheantais anseo de réir mar a dhíghlasáiltear iad.",
      +    },
      +    guide: {
      +      tiers_header: "Leibhéil",
      +      secret_header: "[redacted:secret-assignment] rúnda",
      +      secret_body:
      +        "Coinníonn rúin a dtruicear cruinn faoi cheilt. Nuair a fheiceann Hermes comhartha gaolmhar, athraíonn an cárta go Aimsithe agus taispeánann sé a riachtanas.",
      +      scan_status_header: "Stádas an scanta",
      +      scan_status_body:
      +        "Scanann Hermes an stair logánta uair amháin, ansin feicfear cártaí go huathoibríoch. Níl aon rud sáinnithe má thógann sé cúpla soicind.",
      +      what_scanned_header: "Cad a scantar",
      +      what_scanned_body:
      +        "Sessions, glaonna ar uirlisí, meiteashonraí samhla, earráidí, gnóthachtálacha agus staid díghlasála logánta.",
      +    },
      +    card: {
      +      share_title: "Comhroinn an gnóthachtáil seo",
      +      share_label: "Comhroinn {name}",
      +      share_text: "Comhroinn",
      +      how_to_reveal: "Conas é a nochtadh",
      +      what_counts: "Cad a chomhairtear",
      +      evidence_label: "Fianaise",
      +      evidence_session_fallback: "session",
      +      no_evidence: "Níl fianaise ann fós",
      +    },
      +    latest: {
      +      header: "Díghlasálacha le déanaí",
      +    },
      +    empty: {
      +      no_secrets_header: "Níl aon rúin fhalaithe fágtha sa scan seo.",
      +      no_secrets_body:
      +        "Leid: tosaíonn rúin de ghnáth le patrúin teipe neamhghnácha nó patrúin power-user — coinbhleachtaí poirt, ballaí ceadanna, athróga env in easnamh, botúin YAML, imbhuailtí Docker, úsáid rollback/checkpoint, amais cache, nó mionchóirithe tar éis go leor téacs dheirg.",
      +    },
      +    filters: {
      +      all_categories: "Gach rud",
      +      visibility_all: "uile",
      +      visibility_unlocked: "díghlasáilte",
      +      visibility_discovered: "aimsithe",
      +      visibility_secret: "rún",
      +    },
      +    share: {
      +      dialog_label: "Comhroinn gnóthachtáil",
      +      header: "Comhroinn: {name}",
      +      close: "Dún",
      +      rendering: "Á rindreáil…",
      +      card_alt: "Cárta comhroinnte {name}",
      +      error_generic: "Chuaigh rud éigin amú.",
      +      x_title: "Osclaíonn X le post réamhlíonta",
      +      x_button: "Comhroinn ar X",
      +      copy_title: "Cóipeáil an íomhá le greamú isteach i do phost",
      +      copy_button: "Cóipeáil íomhá",
      +      copied: "Cóipeáilte ✓",
      +      download_button: "Íoslódáil PNG",
      +      hint:
      +        "Osclaíonn Comhroinn ar X post réamhlíonta i gcluaisín nua. Cliceáil Cóipeáil íomhá ar dtús más mian leat an suaitheantas 1200×630 a bheith ceangailte — ligeann X duit é a ghreamú díreach isteach i scríbhneoir an tweet. Sábhálann Íoslódáil PNG an comhad le húsáid áit ar bith.",
      +      clipboard_unsupported:
      +        "Ní thacaítear le cóipeáil íomhá chuig an ngearrthaisce sa bhrabhsálaí seo — úsáid Íoslódáil ina ionad sin.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Clár Kanban á luchtú…",
      +    loadFailed: "Theip ar luchtú an chláir Kanban: ",
      +    loadFailedHint:
      +      "Cruthaíonn an cúl-inneall kanban.db go huathoibríoch ar an gcéad léamh. Má leanann sé seo, féach logaí an dashboard.",
      +    board: "Clár",
      +    newBoard: "+ Clár nua",
      +    newBoardTitle: "Clár nua",
      +    newBoardDescription:
      +      "Ligeann boards duit sruthanna oibre neamhghaolmhara a scaradh — ceann amháin in aghaidh an tionscadail, an repo nó an fhearainn. Ní fheiceann workers ar bhord amháin tascanna board eile riamh.",
      +    slug: "Slug",
      +    slugHint: "— litreacha beaga, fleiscíní, m.sh. atm10-server",
      +    displayName: "Ainm taispeána",
      +    displayNameHint: "(roghnach)",
      +    description: "Cur síos",
      +    descriptionHint: "(roghnach)",
      +    icon: "Deilbhín",
      +    iconHint: "(carachtar amháin nó emoji)",
      +    switchAfterCreate: "Athraigh chuig an gclár seo tar éis a chruthaithe",
      +    cancel: "Cealaigh",
      +    creating: "Á chruthú…",
      +    createBoard: "Cruthaigh clár",
      +    search: "Cuardaigh",
      +    filterCards: "Scag cártaí…",
      +    tenant: "Tenant",
      +    allTenants: "Gach tenant",
      +    assignee: "Sannaí",
      +    allProfiles: "Gach profile",
      +    showArchived: "Taispeáin cinn cartlannaithe",
      +    lanesByProfile: "Lánaí de réir profile",
      +    nudgeDispatcher: "Spreag an dispatcher",
      +    refresh: "Athnuaigh",
      +    selected: "roghnaithe",
      +    complete: "Cuir i gcrích",
      +    archive: "Cartlannaigh",
      +    apply: "Cuir i bhfeidhm",
      +    clear: "Glan",
      +    createTask: "Cruthaigh tasc sa cholún seo",
      +    noTasks: "— gan tascanna —",
      +    unassigned: "gan sannadh",
      +    untitled: "(gan teideal)",
      +    loadingDetail: "Á luchtú…",
      +    addComment: "Cuir nóta tráchta… (Enter chun seoladh)",
      +    comment: "Nóta tráchta",
      +    status: "Stádas",
      +    workspace: "Workspace",
      +    skills: "Scileanna",
      +    createdBy: "Cruthaithe ag",
      +    result: "Toradh",
      +    comments: "Nótaí tráchta",
      +    events: "Imeachtaí",
      +    runHistory: "Stair na rití",
      +    workerLog: "Loga an worker",
      +    loadingLog: "Loga á luchtú…",
      +    noWorkerLog:
      +      "— níl loga worker ann fós (níor sheol an tasc nó rinneadh an loga a rothlú) —",
      +    noDescription: "— gan cur síos —",
      +    noComments: "— gan nótaí tráchta —",
      +    edit: "cuir in eagar",
      +    save: "Sábháil",
      +    dependencies: "Spleáchais",
      +    parents: "Tuismitheoirí:",
      +    children: "Leanaí:",
      +    none: "ceann ar bith",
      +    addParent: "— cuir tuismitheoir leis —",
      +    addChild: "— cuir leanbh leis —",
      +    removeDependency: "Bain spleáchas",
      +    block: "Bac",
      +    unblock: "Díbhac",
      +    notifyHomeChannels: "Cuir cainéil bhaile ar an eolas",
      +    diagnostics: "Diagnóisic",
      +    hide: "Folaigh",
      +    show: "Taispeáin",
      +    attention: "Aird",
      +    tasksNeedAttention: "tasc ag teastáil aird",
      +    taskNeedsAttention: "Tá aird ag teastáil ó 1 thasc",
      +    diagnostic: "diagnóis",
      +    open: "Oscail",
      +    close: "Dún (Esc)",
      +    reassignTo: "Athshann chuig:",
      +    copied: "Cóipeáilte",
      +    copyCommand: "Cóipeáil ordú chuig an ngearrthaisce",
      +    reclaim: "Athéiligh",
      +    reassign: "Athshann",
      +    renderingError: "Bhuail earráid rindreála an chluaisín Kanban",
      +    reloadView: "Athluchtaigh an radharc",
      +    wsAuthFailed:
      +      "Theip ar fhíordheimhniú WebSocket — athluchtaigh an leathanach chun an comhartha seisiúin a athnuachan.",
      +    markDone: "Marcáil {n} tasc mar críochnaithe?",
      +    markArchived: "Cartlannaigh {n} tasc?",
      +    warning: "Rabhadh",
      +    phantomIds: "ID-anna taibhse:",
      +    active: "gníomhach",
      +    ended: "críochnaithe",
      +    noProfile: "(gan profile)",
      +    showAllAttempts: "Taispeáin gach iarracht",
      +    sendingUpdates: "Nuashonruithe á seoladh chuig",
      +    sendNotifications: "Seol fógraí completed / blocked / gave_up chuig",
      +    archiveBoardConfirm:
      +      "Cartlannaigh an clár '{name}'? Bogfar é go boards/_archived/ ionas gur féidir é a aisghabháil níos déanaí. Ní bheidh tascanna an chláir seo le feiceáil aon áit san UI a thuilleadh.",
      +    archiveBoardTitle: "Cartlannaigh an clár seo",
      +    boardSwitcherHint: "Ligeann boards duit sruthanna oibre neamhghaolmhara a scaradh",
      +    taskCreatedWarning: "Cruthaíodh an tasc, ach: ",
      +    moveFailed: "Theip ar an mbogadh: ",
      +    bulkFailed: "Cnuasach: ",
      +    completionBlockedHallucination: "⚠ Cuireadh bac ar chríochnú — ID-anna taibhse na gcártaí",
      +    suspectedHallucinatedReferences: "⚠ Tagairt sa téacs do ID-anna taibhse na gcártaí",
      +    pickProfileFirst: "Roghnaigh profile ar dtús.",
      +    unblockedMessage: "Díbhacadh {id}. Tá an tasc réidh don chéad tic eile.",
      +    unblockFailed: "Theip ar an díbhacadh: ",
      +    reclaimedMessage: "Athéilíodh {id}. Tá an tasc ar ais ag ready.",
      +    reclaimFailed: "Theip ar an athéileamh: ",
      +    reassignedMessage: "Athshannadh {id} chuig {profile}.",
      +    reassignFailed: "Theip ar an athshannadh: ",
      +    selectForBulk: "Roghnaigh do ghníomhartha cnuasaigh",
      +    clickToEdit: "Cliceáil chun eagarthóireacht a dhéanamh",
      +    clickToEditAssignee: "Cliceáil chun an sannaí a chur in eagar",
      +    emptyAssignee: "(folamh = bain an sannadh)",
      +    columnLabels: {
      +      triage: "Triáiseáil",
      +      todo: "Le déanamh",
      +      ready: "Réidh",
      +      running: "Ar siúl",
      +      blocked: "Bactha",
      +      done: "Críochnaithe",
      +      archived: "Cartlannaithe",
      +    },
      +    columnHelp: {
      +      triage: "Smaointe amha — déanfaidh specifier an spec a chur i bhfeidhm",
      +      todo: "Ag fanacht ar spleáchais nó gan sannadh",
      +      ready: "Sannta agus ag fanacht ar thic an dispatcher",
      +      running: "Éilithe ag worker — ar siúl",
      +      blocked: "D'iarr an worker ionchur duine",
      +      done: "Críochnaithe",
      +      archived: "Cartlannaithe",
      +    },
      +    confirmDone:
      +      "Marcáil an tasc seo mar críochnaithe? Scaoiltear éileamh an worker agus éiríonn leanaí spleácha ready.",
      +    confirmArchive:
      +      "Cartlannaigh an tasc seo? Imíonn sé as an réamhradharc cláir.",
      +    confirmBlocked:
      +      "Marcáil an tasc seo mar bactha? Scaoiltear éileamh an worker.",
      +    completionSummary:
      +      "Achoimre chríochnaithe ar {label}. Stóráiltear é seo mar result an taisc.",
      +    completionSummaryRequired:
      +      "Tá achoimre chríochnaithe riachtanach sula marcáiltear tasc mar críochnaithe.",
      +    triagePlaceholder: "Smaoineamh garbh — déanfaidh AI an spec…",
      +    taskTitlePlaceholder: "Teideal taisc nua…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "sannaí",
      +    priority: "Tosaíocht",
      +    skillsPlaceholder:
      +      "scileanna (roghnach, scartha le camóga): translation, github-code-review",
      +    noParent: "— gan tuismitheoir —",
      +    workspacePathDir: "conair workspace (riachtanach, m.sh. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "conair workspace (roghnach, díorthaithe ón sannaí má tá sé folamh)",
      +    logTruncated: "(taispeántar an 100 KB deireanach — loga iomlán ag ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/hu.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const hu: Translations = {
      +  common: {
      +    save: "Mentés",
      +    saving: "Mentés...",
      +    cancel: "Mégse",
      +    close: "Bezárás",
      +    confirm: "Megerősítés",
      +    delete: "Törlés",
      +    refresh: "Frissítés",
      +    retry: "Újra",
      +    search: "Keresés...",
      +    loading: "Betöltés...",
      +    create: "Létrehozás",
      +    creating: "Létrehozás...",
      +    set: "Beállítás",
      +    replace: "Csere",
      +    clear: "Törlés",
      +    live: "Élő",
      +    off: "Ki",
      +    enabled: "engedélyezve",
      +    disabled: "letiltva",
      +    active: "aktív",
      +    inactive: "inaktív",
      +    unknown: "ismeretlen",
      +    untitled: "Névtelen",
      +    none: "Nincs",
      +    form: "Űrlap",
      +    noResults: "Nincs találat",
      +    of: "/",
      +    page: "Oldal",
      +    msgs: "üzenet",
      +    tools: "eszközök",
      +    match: "egyezés",
      +    other: "Egyéb",
      +    configured: "beállítva",
      +    removed: "eltávolítva",
      +    failedToToggle: "Nem sikerült átváltani",
      +    failedToRemove: "Nem sikerült eltávolítani",
      +    failedToReveal: "Nem sikerült megjeleníteni",
      +    collapse: "Összecsukás",
      +    expand: "Kibontás",
      +    general: "Általános",
      +    messaging: "Üzenetküldés",
      +    pluginLoadFailed:
      +      "Nem sikerült betölteni a bővítmény szkriptjét. Ellenőrizze a Network fület (dashboard-plugins/…) és a kiszolgáló bővítmény-elérési útját.",
      +    pluginNotRegistered:
      +      "A bővítmény szkriptje nem hívta meg a register() függvényt, vagy hibára futott. A részletekért nyissa meg a böngésző konzolját.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Navigáció bezárása",
      +    closeModelTools: "Modell és eszközök bezárása",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Aktív munkamenetek:",
      +    gatewayStatusLabel: "Átjáró állapota:",
      +    gatewayStrip: {
      +      failed: "Indítás sikertelen",
      +      off: "Ki",
      +      running: "Fut",
      +      starting: "Indul",
      +      stopped: "Leállítva",
      +    },
      +    nav: {
      +      analytics: "Analitika",
      +      chat: "Csevegés",
      +      config: "Beállítások",
      +      cron: "Cron",
      +      documentation: "Dokumentáció",
      +      keys: "Kulcsok",
      +      logs: "Naplók",
      +      models: "Modellek",
      +      profiles: "profilok: több ügynök",
      +      plugins: "Bővítmények",
      +      sessions: "Munkamenetek",
      +      skills: "Készségek",
      +    },
      +    modelToolsSheetSubtitle: "és eszközök",
      +    modelToolsSheetTitle: "Modell",
      +    navigation: "Navigáció",
      +    openDocumentation: "Dokumentáció megnyitása új lapon",
      +    openNavigation: "Navigáció megnyitása",
      +    pluginNavSection: "Bővítmények",
      +    sessionsActiveCount: "{count} aktív",
      +    statusOverview: "Állapot áttekintése",
      +    system: "Rendszer",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Művelet sikertelen",
      +    actionFinished: "Befejezve",
      +    actions: "Műveletek",
      +    agent: "Ügynök",
      +    activeSessions: "Aktív munkamenetek",
      +    connected: "Csatlakoztatva",
      +    connectedPlatforms: "Csatlakoztatott platformok",
      +    disconnected: "Lekapcsolva",
      +    error: "Hiba",
      +    failed: "Sikertelen",
      +    gateway: "Átjáró",
      +    gatewayFailedToStart: "Az átjáró nem indult el",
      +    lastUpdate: "Utolsó frissítés",
      +    noneRunning: "Nincs",
      +    notRunning: "Nem fut",
      +    pid: "PID",
      +    platformDisconnected: "lekapcsolva",
      +    platformError: "hiba",
      +    recentSessions: "Legutóbbi munkamenetek",
      +    restartGateway: "Átjáró újraindítása",
      +    restartingGateway: "Átjáró újraindítása…",
      +    running: "Fut",
      +    runningRemote: "Fut (távoli)",
      +    startFailed: "Indítás sikertelen",
      +    starting: "Indul",
      +    startedInBackground: "Háttérben elindítva — kövesse a naplókat a folyamathoz",
      +    stopped: "Leállítva",
      +    updateHermes: "Hermes frissítése",
      +    updatingHermes: "Hermes frissítése…",
      +    waitingForOutput: "Várakozás a kimenetre…",
      +  },
      +
      +  sessions: {
      +    title: "Munkamenetek",
      +    searchPlaceholder: "Keresés üzenettartalomban...",
      +    noSessions: "Még nincsenek munkamenetek",
      +    noMatch: "Nincs a keresésnek megfelelő munkamenet",
      +    startConversation: "Indítson egy beszélgetést, hogy itt megjelenjen",
      +    noMessages: "Nincsenek üzenetek",
      +    untitledSession: "Névtelen munkamenet",
      +    deleteSession: "Munkamenet törlése",
      +    confirmDeleteTitle: "Törli a munkamenetet?",
      +    confirmDeleteMessage:
      +      "Ez véglegesen eltávolítja a beszélgetést és minden üzenetét. A művelet nem vonható vissza.",
      +    sessionDeleted: "Munkamenet törölve",
      +    failedToDelete: "Nem sikerült törölni a munkamenetet",
      +    resumeInChat: "Folytatás a csevegésben",
      +    previousPage: "Előző oldal",
      +    nextPage: "Következő oldal",
      +    roles: {
      +      user: "Felhasználó",
      +      assistant: "Asszisztens",
      +      system: "Rendszer",
      +      tool: "Eszköz",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Időszak:",
      +    totalTokens: "[redacted:secret-assignment] token",
      +    totalSessions: "Összes munkamenet",
      +    apiCalls: "API-hívások",
      +    dailyTokenUsage: "Napi tokenhasználat",
      +    dailyBreakdown: "Napi bontás",
      +    perModelBreakdown: "Modellek szerinti bontás",
      +    topSkills: "Legnépszerűbb készségek",
      +    skill: "Készség",
      +    loads: "Ügynök által betöltve",
      +    edits: "Ügynök által kezelve",
      +    lastUsed: "Utoljára használva",
      +    input: "Bemenet",
      +    output: "Kimenet",
      +    total: "Összesen",
      +    noUsageData: "Nincs használati adat erre az időszakra",
      +    startSession: "Indítson munkamenetet az analitika megtekintéséhez",
      +    date: "Dátum",
      +    model: "Modell",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/nap átlag",
      +    acrossModels: "{count} modellen át",
      +    inOut: "{input} be / {output} ki",
      +  },
      +
      +  models: {
      +    modelsUsed: "Használt modellek",
      +    estimatedCost: "Becsült költség",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "munkamenetek",
      +    avgPerSession: "átlag/munkamenet",
      +    apiCalls: "API-hívások",
      +    toolCalls: "eszközhívások",
      +    noModelsData: "Nincs modellhasználati adat erre az időszakra",
      +    startSession: "Indítson munkamenetet a modelladatok megtekintéséhez",
      +  },
      +
      +  logs: {
      +    title: "Naplók",
      +    autoRefresh: "Automatikus frissítés",
      +    file: "Fájl",
      +    level: "Szint",
      +    component: "Komponens",
      +    lines: "Sorok",
      +    noLogLines: "Nem található naplóbejegyzés",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Ez eltávolítja a feladatot az ütemezésből. A művelet nem vonható vissza.",
      +    confirmDeleteTitle: "Törli az ütemezett feladatot?",
      +    newJob: "Új Cron-feladat",
      +    nameOptional: "Név (opcionális)",
      +    namePlaceholder: "pl. Napi összegzés",
      +    prompt: "Prompt",
      +    promptPlaceholder: "Mit tegyen az ügynök minden futtatáskor?",
      +    schedule: "Ütemezés (cron-kifejezés)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Kézbesítés ide",
      +    scheduledJobs: "Ütemezett feladatok",
      +    noJobs: "Nincs beállított cron-feladat. Hozzon létre egyet fent.",
      +    last: "Utolsó",
      +    next: "Következő",
      +    pause: "Szüneteltetés",
      +    resume: "Folytatás",
      +    triggerNow: "Indítás most",
      +    delivery: {
      +      local: "Helyi",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Új profil",
      +    name: "Név",
      +    namePlaceholder: "pl. coder, writer stb.",
      +    nameRequired: "A név kötelező",
      +    nameRule:
      +      "Csak kisbetűk, számjegyek, _ és - karakterek; betűvel vagy számjeggyel kell kezdődnie; legfeljebb 64 karakter.",
      +    invalidName: "Érvénytelen profilnév",
      +    cloneFromDefault: "Konfiguráció klónozása az alapértelmezett profilból",
      +    allProfiles: "Profilok",
      +    noProfiles: "Nem található profil.",
      +    defaultBadge: "alapértelmezett",
      +    hasEnv: "env",
      +    model: "Modell",
      +    skills: "Készségek",
      +    rename: "Átnevezés",
      +    editSoul: "SOUL.md szerkesztése",
      +    soulSection: "SOUL.md (személyiség / rendszerprompt)",
      +    soulPlaceholder: "# Hogyan viselkedjen ez az ügynök…",
      +    saveSoul: "SOUL mentése",
      +    soulSaved: "SOUL.md mentve",
      +    openInTerminal: "CLI-parancs másolása",
      +    commandCopied: "Vágólapra másolva",
      +    copyFailed: "Nem sikerült másolni",
      +    confirmDeleteTitle: "Törli a profilt?",
      +    confirmDeleteMessage:
      +      "Ez véglegesen törli a(z) '{name}' profilt — konfigurációt, kulcsokat, emlékeket, munkameneteket, készségeket, cron-feladatokat. A művelet nem vonható vissza.",
      +    created: "Létrehozva",
      +    deleted: "Törölve",
      +    renamed: "Átnevezve",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Kontextusmotor",
      +    dashboardSlots: "Vezérlőpult-slotok",
      +    disableRuntime: "Letiltás",
      +    enableAfterInstall: "Engedélyezés a telepítés után",
      +    enableRuntime: "Engedélyezés",
      +    forceReinstall: "Kényszerített újratelepítés (a meglévő mappa előbb törlődik)",
      +    headline:
      +      "Hermes-bővítmények felfedezése, telepítése, engedélyezése és frissítése (a `hermes plugins` paritás).",
      +    identifierLabel: "Git URL vagy owner/repo",
      +    inactive: "inaktív",
      +    installBtn: "Telepítés Gitből",
      +    installHeading: "Telepítés GitHubról / Git URL-ről",
      +    installHint: "Használjon owner/repo rövidítést vagy teljes https:// vagy git@ klónozási URL-t.",
      +    memoryProviderLabel: "Memória-szolgáltató",
      +    missingEnvWarn: "Állítsa be ezeket a Kulcsok között, mielőtt a bővítmény futhatna:",
      +    noDashboardTab: "Nincs vezérlőpult-fül",
      +    openTab: "Megnyitás",
      +    orphanHeading: "Csak vezérlőpult-bővítmények (nincs egyező agent plugin.yaml)",
      +    pluginListHeading: "Telepített bővítmények",
      +    providerDefaults: "beépített / alapértelmezett",
      +    providersHeading: "Futási idejű szolgáltató-bővítmények",
      +    providersHint:
      +      "A memory.provider (üres = beépített) és a context.engine értékét írja a config.yaml fájlba. A következő munkamenetben lép életbe.",
      +    refreshDashboard: "Vezérlőpult-bővítmények újraolvasása",
      +    removeConfirm: "Eltávolítja ezt a bővítményt a ~/.hermes/plugins/ mappából?",
      +    removeHint: "Csak a felhasználó által a ~/.hermes/plugins alá telepített bővítmények távolíthatók el.",
      +    rescanHeading: "SPA-bővítményregiszter",
      +    rescanHint: "Olvassa újra a fájlokat a lemezen történő hozzáadás után, hogy az oldalsáv felvegye az új manifesteket.",
      +    runtimeHeading: "Átjáró-futási idő (YAML-bővítmények)",
      +    saveProviders: "Szolgáltatóbeállítások mentése",
      +    savedProviders: "Szolgáltatóbeállítások mentve.",
      +    sourceBadge: "Forrás",
      +    authRequired: "Hitelesítés szükséges",
      +    authRequiredHint: "Futtassa ezt a parancsot a hitelesítéshez:",
      +    updateGit: "Git pull",
      +    versionBadge: "Verzió",
      +    showInSidebar: "Megjelenítés az oldalsávon",
      +    hideFromSidebar: "Elrejtés az oldalsávról",
      +  },
      +
      +  skills: {
      +    title: "Készségek",
      +    searchPlaceholder: "Készségek és eszközkészletek keresése...",
      +    enabledOf: "{enabled}/{total} engedélyezve",
      +    all: "Összes",
      +    categories: "Kategóriák",
      +    filters: "Szűrők",
      +    noSkills: "Nem található készség. A készségek a ~/.hermes/skills/ mappából töltődnek be",
      +    noSkillsMatch: "Nincs a keresésnek vagy szűrőnek megfelelő készség.",
      +    skillCount: "{count} készség{s}",
      +    resultCount: "{count} találat{s}",
      +    noDescription: "Nincs elérhető leírás.",
      +    toolsets: "Eszközkészletek",
      +    toolsetLabel: "{name} eszközkészlet",
      +    noToolsetsMatch: "Nincs a keresésnek megfelelő eszközkészlet.",
      +    setupNeeded: "Beállítás szükséges",
      +    disabledForCli: "CLI-hez letiltva",
      +    more: "+{count} további",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Szűrők",
      +    sections: "Szakaszok",
      +    exportConfig: "Konfiguráció exportálása JSON-ba",
      +    importConfig: "Konfiguráció importálása JSON-ból",
      +    resetDefaults: "Visszaállítás alapértelmezettre",
      +    resetScopeTooltip: "{scope} visszaállítása alapértelmezettre",
      +    confirmResetScope: "Visszaállítja az összes {scope} beállítást alapértelmezettre? Ez csak az űrlapot frissíti — a változások nem íródnak be a config.yaml fájlba, amíg meg nem nyomja a Mentés gombot.",
      +    resetScopeToast: "{scope} visszaállítva alapértelmezettre — ellenőrizze és mentse a megőrzéshez",
      +    rawYaml: "Nyers YAML-konfiguráció",
      +    searchResults: "Keresési eredmények",
      +    fields: "mező{s}",
      +    noFieldsMatch: 'Nincs a(z) "{query}" keresésnek megfelelő mező',
      +    configSaved: "Konfiguráció mentve",
      +    yamlConfigSaved: "YAML-konfiguráció mentve",
      +    failedToSave: "Mentés sikertelen",
      +    failedToSaveYaml: "YAML mentése sikertelen",
      +    failedToLoadRaw: "Nem sikerült betölteni a nyers konfigurációt",
      +    configImported: "Konfiguráció importálva — ellenőrizze és mentse",
      +    invalidJson: "Érvénytelen JSON-fájl",
      +    categories: {
      +      general: "Általános",
      +      agent: "Ügynök",
      +      terminal: "Terminál",
      +      display: "Megjelenítés",
      +      delegation: "Delegálás",
      +      memory: "Memória",
      +      compression: "Tömörítés",
      +      security: "Biztonság",
      +      browser: "Böngésző",
      +      voice: "Hang",
      +      tts: "Szövegfelolvasás",
      +      stt: "Beszédfelismerés",
      +      logging: "Naplózás",
      +      discord: "Discord",
      +      auxiliary: "Kiegészítő",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "A változások azonnal mentésre kerülnek a lemezre. Az aktív munkamenetek automatikusan átveszik az új kulcsokat.",
      +    confirmClearMessage:
      +      "A változó tárolt értéke törlődik a .env fájlból. Ez a felületről nem vonható vissza.",
      +    confirmClearTitle: "Törli ezt a kulcsot?",
      +    description: "API-kulcsok és titkok kezelése a következő helyen:",
      +    hideAdvanced: "Speciális elrejtése",
      +    showAdvanced: "Speciális megjelenítése",
      +    llmProviders: "LLM-szolgáltatók",
      +    providersConfigured: "{configured} / {total} szolgáltató beállítva",
      +    getKey: "Kulcs lekérése",
      +    notConfigured: "{count} nincs beállítva",
      +    notSet: "Nincs beállítva",
      +    keysCount: "{count} kulcs{s}",
      +    enterValue: "Adjon meg értéket...",
      +    replaceCurrentValue: "Jelenlegi érték cseréje ({preview})",
      +    showValue: "Tényleges érték megjelenítése",
      +    hideValue: "Érték elrejtése",
      +  },
      +
      +  oauth: {
      +    title: "Szolgáltatói bejelentkezések (OAuth)",
      +    providerLogins: "Szolgáltatói bejelentkezések (OAuth)",
      +    description: "{connected} / {total} OAuth-szolgáltató csatlakoztatva. A bejelentkezési folyamat jelenleg a CLI-n keresztül fut; kattintson a Parancs másolása gombra, és illessze be egy terminálba a beállításhoz.",
      +    connected: "Csatlakoztatva",
      +    expired: "Lejárt",
      +    notConnected: "Nincs csatlakoztatva. Futtassa a {command} parancsot egy terminálban.",
      +    runInTerminal: "egy terminálban.",
      +    noProviders: "Nem észlelhető OAuth-képes szolgáltató.",
      +    login: "Bejelentkezés",
      +    disconnect: "Lecsatlakozás",
      +    managedExternally: "Külsőleg kezelt",
      +    copied: "Másolva ✓",
      +    cli: "CLI",
      +    copyCliCommand: "CLI-parancs másolása (külső / tartalék)",
      +    connect: "Csatlakozás",
      +    sessionExpires: "A munkamenet {time} múlva lejár",
      +    initiatingLogin: "Bejelentkezési folyamat indítása…",
      +    exchangingCode: "Kód cseréje tokenekre…",
      +    connectedClosing: "Csatlakoztatva! Bezárás…",
      +    loginFailed: "A bejelentkezés sikertelen.",
      +    sessionExpired: "A munkamenet lejárt. Kattintson az Újra gombra új bejelentkezéshez.",
      +    reOpenAuth: "Hitelesítési oldal újranyitása",
      +    reOpenVerification: "Ellenőrzési oldal újranyitása",
      +    submitCode: "Kód beküldése",
      +    pasteCode: "Illessze be a hitelesítési kódot (a #state utótaggal együtt is megfelel)",
      +    waitingAuth: "Várakozás a böngészőben történő engedélyezésre…",
      +    enterCodePrompt: "Új lap nyílt meg. Adja meg ezt a kódot, ha kéri:",
      +    pkceStep1: "Új lap nyílt meg a claude.ai oldalra. Jelentkezzen be, és kattintson az Authorize gombra.",
      +    pkceStep2: "Másolja ki az engedélyezés után megjelenő hitelesítési kódot.",
      +    pkceStep3: "Illessze be alább, és küldje be.",
      +    flowLabels: {
      +      pkce: "Bejelentkezés böngészőből (PKCE)",
      +      device_code: "Eszközkód",
      +      external: "Külső CLI",
      +    },
      +    expiresIn: "lejár {time} múlva",
      +  },
      +
      +  language: {
      +    switchTo: "Váltás angolra",
      +  },
      +
      +  theme: {
      +    title: "Téma",
      +    switchTheme: "Téma váltása",
      +  },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Gyűjthető Hermes-jelvények, valós munkamenet-előzmények alapján szerezve. Az ismert, de még nem szerzett teljesítmények Felfedezettként jelennek meg; a Titkos teljesítmények rejtve maradnak az első egyező viselkedésig.",
      +      scan_subtitle:
      +        "Hermes munkamenet-előzmények vizsgálata. Az első vizsgálat 5–10 másodpercig is eltarthat nagy előzmények esetén.",
      +    },
      +    actions: {
      +      rescan: "Újravizsgálat",
      +    },
      +    stats: {
      +      unlocked: "Feloldva",
      +      unlocked_hint: "megszerzett jelvények",
      +      discovered: "Felfedezve",
      +      discovered_hint: "ismert, még nem szerzett",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] az első jelzésig",
      +      highest_tier: "Legmagasabb szint",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Legutóbbi",
      +      latest_hint_empty: "futtasd többet a Hermest",
      +      none_yet: "Még semmi",
      +    },
      +    state: {
      +      unlocked: "Feloldva",
      +      discovered: "Felfedezve",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Cél: {tier}",
      +      hidden: "Rejtett",
      +      complete: "Kész",
      +      objective: "Cél",
      +    },
      +    progress: {
      +      hidden: "rejtett",
      +    },
      +    scan: {
      +      building_headline: "Teljesítményprofil építése…",
      +      building_detail:
      +        "Munkamenetek, eszközhívások, modell-metaadatok és feloldási állapot olvasása.",
      +      starting_headline: "Teljesítmény-vizsgálat indítása…",
      +      progress_detail:
      +        "{scanned} / {total} munkamenet vizsgálva · {pct}%. A jelvények a további előzmények beolvasásával oldódnak fel.",
      +      idle_detail:
      +        "Munkamenetek, eszközhívások, modell-metaadatok és feloldási állapot olvasása. A jelvények itt jelennek meg, ahogy feloldódnak.",
      +    },
      +    guide: {
      +      tiers_header: "Szintek",
      +      secret_header: "[redacted:secret-assignment] teljesítmények",
      +      secret_body:
      +        "A titkos teljesítmények elrejtik a pontos kiváltó eseményt. Amint a Hermes kapcsolódó jelet észlel, a kártya Felfedezettre vált, és megjeleníti a követelményt.",
      +      scan_status_header: "Vizsgálat állapota",
      +      scan_status_body:
      +        "A Hermes egyszer átvizsgálja a helyi előzményeket, majd a kártyák automatikusan megjelennek. Semmi sem akadt el, ha ez néhány másodpercig tart.",
      +      what_scanned_header: "Mit vizsgálunk",
      +      what_scanned_body:
      +        "Munkamenetek, eszközhívások, modell-metaadatok, hibák, teljesítmények és helyi feloldási állapot.",
      +    },
      +    card: {
      +      share_title: "Teljesítmény megosztása",
      +      share_label: "{name} megosztása",
      +      share_text: "Megosztás",
      +      how_to_reveal: "Hogyan fedhető fel",
      +      what_counts: "Mi számít",
      +      evidence_label: "Bizonyíték",
      +      evidence_session_fallback: "munkamenet",
      +      no_evidence: "Még nincs bizonyíték",
      +    },
      +    latest: {
      +      header: "Legutóbbi feloldások",
      +    },
      +    empty: {
      +      no_secrets_header: "Ebben a vizsgálatban nem maradt rejtett titok.",
      +      no_secrets_body:
      +        "Tipp: a titkok általában szokatlan hibákból vagy haladó felhasználói mintákból indulnak — portütközések, jogosultsági falak, hiányzó környezeti változók, YAML-hibák, Docker-ütközések, rollback/checkpoint használata, gyorsítótár-találatok vagy apró javítások sok piros szöveg után.",
      +    },
      +    filters: {
      +      all_categories: "Összes",
      +      visibility_all: "összes",
      +      visibility_unlocked: "feloldott",
      +      visibility_discovered: "felfedezett",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Teljesítmény megosztása",
      +      header: "Megosztás: {name}",
      +      close: "Bezárás",
      +      rendering: "Renderelés…",
      +      card_alt: "{name} megosztókártya",
      +      error_generic: "Valami hiba történt.",
      +      x_title: "Megnyitja az X-et előre kitöltött bejegyzéssel",
      +      x_button: "Megosztás az X-en",
      +      copy_title: "Kép másolása a bejegyzésbe való beillesztéshez",
      +      copy_button: "Kép másolása",
      +      copied: "Másolva ✓",
      +      download_button: "PNG letöltése",
      +      hint:
      +        "A „Megosztás az X-en” új lapon nyit meg egy előre kitöltött bejegyzést. Először kattints a „Kép másolása” gombra, ha az 1200×630-as jelvényt is csatolnád — az X engedi, hogy közvetlenül beillesszd a bejegyzésszerkesztőbe. A „PNG letöltése” bárhol felhasználható fájlként menti.",
      +      clipboard_unsupported:
      +        "A kép vágólapra másolása nem támogatott ebben a böngészőben — használd inkább a Letöltést.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Kanban tábla betöltése…",
      +    loadFailed: "Nem sikerült betölteni a Kanban táblát: ",
      +    loadFailedHint:
      +      "A backend első olvasáskor automatikusan létrehozza a kanban.db fájlt. Ha továbbra is fennáll, ellenőrizd a dashboard naplóit.",
      +    board: "Tábla",
      +    newBoard: "+ Új tábla",
      +    newBoardTitle: "Új tábla",
      +    newBoardDescription:
      +      "A táblákkal külön tudod választani az egymással nem összefüggő munkafolyamokat — egyet projektenként, repónként vagy területenként. Az egyik tábla workerei sosem látják a másik tábla feladatait.",
      +    slug: "Slug",
      +    slugHint: "— kisbetűk, kötőjelek, pl. atm10-server",
      +    displayName: "Megjelenítendő név",
      +    displayNameHint: "(opcionális)",
      +    description: "Leírás",
      +    descriptionHint: "(opcionális)",
      +    icon: "Ikon",
      +    iconHint: "(egyetlen karakter vagy emodzsi)",
      +    switchAfterCreate: "Váltás erre a táblára létrehozás után",
      +    cancel: "Mégse",
      +    creating: "Létrehozás…",
      +    createBoard: "Tábla létrehozása",
      +    search: "Keresés",
      +    filterCards: "Kártyák szűrése…",
      +    tenant: "Tenant",
      +    allTenants: "Összes tenant",
      +    assignee: "Felelős",
      +    allProfiles: "Összes profil",
      +    showArchived: "Archiváltak megjelenítése",
      +    lanesByProfile: "Sávok profil szerint",
      +    nudgeDispatcher: "Dispatcher noszogatása",
      +    refresh: "Frissítés",
      +    selected: "kiválasztva",
      +    complete: "Befejezés",
      +    archive: "Archiválás",
      +    apply: "Alkalmaz",
      +    clear: "Törlés",
      +    createTask: "Feladat létrehozása ebben az oszlopban",
      +    noTasks: "— nincsenek feladatok —",
      +    unassigned: "nincs felelős",
      +    untitled: "(névtelen)",
      +    loadingDetail: "Betöltés…",
      +    addComment: "Hozzászólás hozzáadása… (Enter a beküldéshez)",
      +    comment: "Hozzászólás",
      +    status: "Állapot",
      +    workspace: "Munkaterület",
      +    skills: "Készségek",
      +    createdBy: "Létrehozta",
      +    result: "Eredmény",
      +    comments: "Hozzászólások",
      +    events: "Események",
      +    runHistory: "Futási előzmények",
      +    workerLog: "Worker napló",
      +    loadingLog: "Napló betöltése…",
      +    noWorkerLog:
      +      "— még nincs worker napló (a feladat nem indult el, vagy a napló rotálódott) —",
      +    noDescription: "— nincs leírás —",
      +    noComments: "— nincsenek hozzászólások —",
      +    edit: "szerkesztés",
      +    save: "Mentés",
      +    dependencies: "Függőségek",
      +    parents: "Szülők:",
      +    children: "Gyermekek:",
      +    none: "nincs",
      +    addParent: "— szülő hozzáadása —",
      +    addChild: "— gyermek hozzáadása —",
      +    removeDependency: "Függőség eltávolítása",
      +    block: "Blokkolás",
      +    unblock: "Feloldás",
      +    notifyHomeChannels: "Otthoni csatornák értesítése",
      +    diagnostics: "Diagnosztika",
      +    hide: "Elrejtés",
      +    show: "Megjelenítés",
      +    attention: "Figyelem",
      +    tasksNeedAttention: "feladat figyelmet igényel",
      +    taskNeedsAttention: "1 feladat figyelmet igényel",
      +    diagnostic: "diagnosztika",
      +    open: "Megnyitás",
      +    close: "Bezárás (Esc)",
      +    reassignTo: "Új felelős:",
      +    copied: "Másolva",
      +    copyCommand: "Parancs másolása a vágólapra",
      +    reclaim: "Visszavétel",
      +    reassign: "Újrakiosztás",
      +    renderingError: "A Kanban fülön renderelési hiba lépett fel",
      +    reloadView: "Nézet újratöltése",
      +    wsAuthFailed:
      +      "WebSocket-hitelesítés sikertelen — töltsd újra az oldalt a munkamenet-token frissítéséhez.",
      +    markDone: "Megjelölöd {n} feladatot késznek?",
      +    markArchived: "Archiválsz {n} feladatot?",
      +    warning: "Figyelmeztetés",
      +    phantomIds: "Fantom id-k:",
      +    active: "aktív",
      +    ended: "befejeződött",
      +    noProfile: "(nincs profil)",
      +    showAllAttempts: "Összes próbálkozás megjelenítése",
      +    sendingUpdates: "Frissítések küldése ide:",
      +    sendNotifications: "completed / blocked / gave_up értesítések küldése ide:",
      +    archiveBoardConfirm:
      +      "Archiválod a(z) '{name}' táblát? Áthelyezzük a boards/_archived/ mappába, hogy később visszaállíthasd. A táblán lévő feladatok többé nem jelennek meg sehol az UI-ban.",
      +    archiveBoardTitle: "Tábla archiválása",
      +    boardSwitcherHint: "A táblákkal külön tudod választani az egymással nem összefüggő munkafolyamokat",
      +    taskCreatedWarning: "Feladat létrehozva, de: ",
      +    moveFailed: "Áthelyezés sikertelen: ",
      +    bulkFailed: "Tömeges: ",
      +    completionBlockedHallucination: "⚠ Befejezés blokkolva — fantom kártya id-k",
      +    suspectedHallucinatedReferences: "⚠ A szöveg fantom kártya id-kre hivatkozott",
      +    pickProfileFirst: "Először válassz profilt.",
      +    unblockedMessage: "{id} feloldva. A feladat készen áll a következő tickre.",
      +    unblockFailed: "Feloldás sikertelen: ",
      +    reclaimedMessage: "{id} visszavéve. A feladat újra ready állapotban van.",
      +    reclaimFailed: "Visszavétel sikertelen: ",
      +    reassignedMessage: "{id} újrakiosztva neki: {profile}.",
      +    reassignFailed: "Újrakiosztás sikertelen: ",
      +    selectForBulk: "Kijelölés tömeges műveletekhez",
      +    clickToEdit: "Kattints a szerkesztéshez",
      +    clickToEditAssignee: "Kattints a felelős szerkesztéséhez",
      +    emptyAssignee: "(üres = felelős eltávolítása)",
      +    columnLabels: {
      +      triage: "Triázs",
      +      todo: "Tennivaló",
      +      ready: "Indulásra kész",
      +      running: "Folyamatban",
      +      blocked: "Blokkolva",
      +      done: "Kész",
      +      archived: "Archivált",
      +    },
      +    columnHelp: {
      +      triage: "Nyers ötletek — egy specifier kidolgozza a specifikációt",
      +      todo: "Függőségekre vár vagy nincs felelőse",
      +      ready: "Kiosztva, dispatcher tickre vár",
      +      running: "Worker felvette — folyamatban",
      +      blocked: "A worker emberi beavatkozást kért",
      +      done: "Befejezve",
      +      archived: "Archiválva",
      +    },
      +    confirmDone:
      +      "Megjelölöd ezt a feladatot késznek? A worker foglalása felszabadul, és a függő gyermekek ready állapotba kerülnek.",
      +    confirmArchive:
      +      "Archiválod ezt a feladatot? Eltűnik az alapértelmezett tábla nézetből.",
      +    confirmBlocked:
      +      "Megjelölöd ezt a feladatot blokkoltként? A worker foglalása felszabadul.",
      +    completionSummary:
      +      "Befejezési összefoglaló a következőhöz: {label}. Ez a feladat eredményeként kerül tárolásra.",
      +    completionSummaryRequired:
      +      "A feladat késznek jelölése előtt kötelező megadni a befejezési összefoglalót.",
      +    triagePlaceholder: "Nyers ötlet — az AI specifikálja…",
      +    taskTitlePlaceholder: "Új feladat címe…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "felelős",
      +    priority: "Prioritás",
      +    skillsPlaceholder:
      +      "készségek (opcionális, vesszővel elválasztva): translation, github-code-review",
      +    noParent: "— nincs szülő —",
      +    workspacePathDir: "munkaterület útvonala (kötelező, pl. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "munkaterület útvonala (opcionális, üresen a felelősből származtatva)",
      +    logTruncated: "(az utolsó 100 KB látható — teljes napló: ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/index.ts
      @@ -1,2 +1,2 @@
      -export { I18nProvider, useI18n } from "./context";
      +export { I18nProvider, useI18n, LOCALE_META } from "./context";
       export type { Locale, Translations } from "./types";
    • web/src/i18n/it.ts
      @@ -0,0 +1,695 @@
      +import type { Translations } from "./types";
      +
      +export const it: Translations = {
      +  common: {
      +    save: "Salva",
      +    saving: "Salvataggio...",
      +    cancel: "Annulla",
      +    close: "Chiudi",
      +    confirm: "Conferma",
      +    delete: "Elimina",
      +    refresh: "Aggiorna",
      +    retry: "Riprova",
      +    search: "Cerca...",
      +    loading: "Caricamento...",
      +    create: "Crea",
      +    creating: "Creazione...",
      +    set: "Imposta",
      +    replace: "Sostituisci",
      +    clear: "Cancella",
      +    live: "In tempo reale",
      +    off: "Spento",
      +    enabled: "abilitato",
      +    disabled: "disabilitato",
      +    active: "attivo",
      +    inactive: "inattivo",
      +    unknown: "sconosciuto",
      +    untitled: "Senza titolo",
      +    none: "Nessuno",
      +    form: "Modulo",
      +    noResults: "Nessun risultato",
      +    of: "di",
      +    page: "Pagina",
      +    msgs: "msg",
      +    tools: "strumenti",
      +    match: "corrispondenza",
      +    other: "Altro",
      +    configured: "configurato",
      +    removed: "rimosso",
      +    failedToToggle: "Commutazione non riuscita",
      +    failedToRemove: "Rimozione non riuscita",
      +    failedToReveal: "Visualizzazione non riuscita",
      +    collapse: "Comprimi",
      +    expand: "Espandi",
      +    general: "Generale",
      +    messaging: "Messaggistica",
      +    pluginLoadFailed:
      +      "Impossibile caricare lo script di questo plugin. Controlla la scheda Network (dashboard-plugins/…) e il percorso dei plugin del server.",
      +    pluginNotRegistered:
      +      "Lo script del plugin non ha chiamato register(), oppure ha generato un errore. Apri la console del browser per i dettagli.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Chiudi navigazione",
      +    closeModelTools: "Chiudi modello e strumenti",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Sessioni attive:",
      +    gatewayStatusLabel: "Stato gateway:",
      +    gatewayStrip: {
      +      failed: "Avvio non riuscito",
      +      off: "Spento",
      +      running: "In esecuzione",
      +      starting: "Avvio in corso",
      +      stopped: "Arrestato",
      +    },
      +    nav: {
      +      analytics: "Analisi",
      +      chat: "Chat",
      +      config: "Configurazione",
      +      cron: "Cron",
      +      documentation: "Documentazione",
      +      keys: "Chiavi",
      +      logs: "Log",
      +      models: "Modelli",
      +      profiles: "profili : multi agent",
      +      plugins: "Plugin",
      +      sessions: "Sessioni",
      +      skills: "Competenze",
      +    },
      +    modelToolsSheetSubtitle: "e strumenti",
      +    modelToolsSheetTitle: "Modello",
      +    navigation: "Navigazione",
      +    openDocumentation: "Apri la documentazione in una nuova scheda",
      +    openNavigation: "Apri navigazione",
      +    pluginNavSection: "Plugin",
      +    sessionsActiveCount: "{count} attive",
      +    statusOverview: "Panoramica dello stato",
      +    system: "Sistema",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Azione non riuscita",
      +    actionFinished: "Completata",
      +    actions: "Azioni",
      +    agent: "Agente",
      +    activeSessions: "Sessioni attive",
      +    connected: "Connesso",
      +    connectedPlatforms: "Piattaforme connesse",
      +    disconnected: "Disconnesso",
      +    error: "Errore",
      +    failed: "Non riuscito",
      +    gateway: "Gateway",
      +    gatewayFailedToStart: "Avvio del gateway non riuscito",
      +    lastUpdate: "Ultimo aggiornamento",
      +    noneRunning: "Nessuno",
      +    notRunning: "Non in esecuzione",
      +    pid: "PID",
      +    platformDisconnected: "disconnesso",
      +    platformError: "errore",
      +    recentSessions: "Sessioni recenti",
      +    restartGateway: "Riavvia gateway",
      +    restartingGateway: "Riavvio del gateway…",
      +    running: "In esecuzione",
      +    runningRemote: "In esecuzione (remoto)",
      +    startFailed: "Avvio non riuscito",
      +    starting: "Avvio in corso",
      +    startedInBackground: "Avviato in background — controlla i log per i progressi",
      +    stopped: "Arrestato",
      +    updateHermes: "Aggiorna Hermes",
      +    updatingHermes: "Aggiornamento di Hermes…",
      +    waitingForOutput: "In attesa di output…",
      +  },
      +
      +  sessions: {
      +    title: "Sessioni",
      +    searchPlaceholder: "Cerca nel contenuto dei messaggi...",
      +    noSessions: "Nessuna sessione",
      +    noMatch: "Nessuna sessione corrisponde alla ricerca",
      +    startConversation: "Avvia una conversazione per vederla qui",
      +    noMessages: "Nessun messaggio",
      +    untitledSession: "Sessione senza titolo",
      +    deleteSession: "Elimina sessione",
      +    confirmDeleteTitle: "Eliminare la sessione?",
      +    confirmDeleteMessage:
      +      "Questa operazione rimuove definitivamente la conversazione e tutti i suoi messaggi. Non può essere annullata.",
      +    sessionDeleted: "Sessione eliminata",
      +    failedToDelete: "Eliminazione della sessione non riuscita",
      +    resumeInChat: "Riprendi nella chat",
      +    previousPage: "Pagina precedente",
      +    nextPage: "Pagina successiva",
      +    roles: {
      +      user: "Utente",
      +      assistant: "Assistente",
      +      system: "Sistema",
      +      tool: "Strumento",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Periodo:",
      +    totalTokens: "Token totali",
      +    totalSessions: "Sessioni totali",
      +    apiCalls: "Chiamate API",
      +    dailyTokenUsage: "[redacted:secret-assignment] giornaliero token",
      +    dailyBreakdown: "Dettaglio giornaliero",
      +    perModelBreakdown: "Dettaglio per modello",
      +    topSkills: "Competenze più usate",
      +    skill: "Competenza",
      +    loads: "Caricato dall'agente",
      +    edits: "Gestito dall'agente",
      +    lastUsed: "Ultimo uso",
      +    input: "Input",
      +    output: "Output",
      +    total: "Totale",
      +    noUsageData: "Nessun dato di utilizzo per questo periodo",
      +    startSession: "Avvia una sessione per vedere le analisi qui",
      +    date: "Data",
      +    model: "Modello",
      +    tokens: "Token",
      +    perDayAvg: "/giorno medio",
      +    acrossModels: "su {count} modelli",
      +    inOut: "{input} in / {output} out",
      +  },
      +
      +  models: {
      +    modelsUsed: "Modelli utilizzati",
      +    estimatedCost: "Costo stim.",
      +    tokens: "token",
      +    sessions: "sessioni",
      +    avgPerSession: "media/sessione",
      +    apiCalls: "chiamate API",
      +    toolCalls: "chiamate strumenti",
      +    noModelsData: "Nessun dato sull'uso dei modelli per questo periodo",
      +    startSession: "Avvia una sessione per vedere i dati dei modelli qui",
      +  },
      +
      +  logs: {
      +    title: "Log",
      +    autoRefresh: "Aggiornamento automatico",
      +    file: "File",
      +    level: "Livello",
      +    component: "Componente",
      +    lines: "Righe",
      +    noLogLines: "Nessuna riga di log trovata",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Questa operazione rimuove l'attività dalla pianificazione. Non può essere annullata.",
      +    confirmDeleteTitle: "Eliminare l'attività pianificata?",
      +    newJob: "Nuova attività cron",
      +    nameOptional: "Nome (facoltativo)",
      +    namePlaceholder: "es. Riepilogo giornaliero",
      +    prompt: "Prompt",
      +    promptPlaceholder: "Cosa deve fare l'agente a ogni esecuzione?",
      +    schedule: "Pianificazione (espressione cron)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Consegna a",
      +    scheduledJobs: "Attività pianificate",
      +    noJobs: "Nessuna attività cron configurata. Creane una sopra.",
      +    last: "Ultima",
      +    next: "Prossima",
      +    pause: "Pausa",
      +    resume: "Riprendi",
      +    triggerNow: "Esegui ora",
      +    delivery: {
      +      local: "Locale",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Nuovo profilo",
      +    name: "Nome",
      +    namePlaceholder: "es. coder, writer, ecc.",
      +    nameRequired: "Il nome è obbligatorio",
      +    nameRule:
      +      "Solo lettere minuscole, cifre, _ e -; deve iniziare con una lettera o cifra; fino a 64 caratteri.",
      +    invalidName: "Nome del profilo non valido",
      +    cloneFromDefault: "Clona la configurazione dal profilo predefinito",
      +    allProfiles: "Profili",
      +    noProfiles: "Nessun profilo trovato.",
      +    defaultBadge: "predefinito",
      +    hasEnv: "env",
      +    model: "Modello",
      +    skills: "Competenze",
      +    rename: "Rinomina",
      +    editSoul: "Modifica SOUL.md",
      +    soulSection: "SOUL.md (personalità / prompt di sistema)",
      +    soulPlaceholder: "# Come dovrebbe comportarsi questo agente…",
      +    saveSoul: "Salva SOUL",
      +    soulSaved: "SOUL.md salvato",
      +    openInTerminal: "Copia comando CLI",
      +    commandCopied: "Copiato negli appunti",
      +    copyFailed: "Impossibile copiare",
      +    confirmDeleteTitle: "Eliminare il profilo?",
      +    confirmDeleteMessage:
      +      "Questa operazione elimina definitivamente il profilo '{name}' — configurazione, chiavi, memorie, sessioni, competenze, attività cron. Non può essere annullata.",
      +    created: "Creato",
      +    deleted: "Eliminato",
      +    renamed: "Rinominato",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Motore di contesto",
      +    dashboardSlots: "Slot del dashboard",
      +    disableRuntime: "Disabilita",
      +    enableAfterInstall: "Abilita dopo l'installazione",
      +    enableRuntime: "Abilita",
      +    forceReinstall: "Forza reinstallazione (elimina prima la cartella esistente)",
      +    headline:
      +      "Scopri, installa, abilita e aggiorna i plugin Hermes (parità con `hermes plugins`).",
      +    identifierLabel: "URL Git o owner/repo",
      +    inactive: "inattivo",
      +    installBtn: "Installa da Git",
      +    installHeading: "Installa da GitHub / URL Git",
      +    installHint: "Usa la forma breve owner/repo o un URL clone https:// o git@ completo.",
      +    memoryProviderLabel: "Provider di memoria",
      +    missingEnvWarn: "Imposta queste variabili in Chiavi prima di eseguire il plugin:",
      +    noDashboardTab: "Nessuna scheda nel dashboard",
      +    openTab: "Apri",
      +    orphanHeading: "Estensioni solo dashboard (nessuna corrispondenza con plugin.yaml)",
      +    pluginListHeading: "Plugin installati",
      +    providerDefaults: "integrato / predefinito",
      +    providersHeading: "Plugin provider runtime",
      +    providersHint:
      +      "Scrive memory.provider (vuoto = integrato) e context.engine in config.yaml. Effetto dalla prossima sessione.",
      +    refreshDashboard: "Riscansiona estensioni dashboard",
      +    removeConfirm: "Rimuovere questo plugin da ~/.hermes/plugins/?",
      +    removeHint: "Solo i plugin installati dall'utente in ~/.hermes/plugins possono essere rimossi.",
      +    rescanHeading: "Registro plugin SPA",
      +    rescanHint: "Riscansiona dopo aver aggiunto file su disco affinché la barra laterale rilevi i nuovi manifest.",
      +    runtimeHeading: "Runtime gateway (plugin YAML)",
      +    saveProviders: "Salva impostazioni provider",
      +    savedProviders: "Impostazioni provider salvate.",
      +    sourceBadge: "Origine",
      +    authRequired: "Autenticazione richiesta",
      +    authRequiredHint: "Esegui questo comando per autenticarti:",
      +    updateGit: "Git pull",
      +    versionBadge: "Versione",
      +    showInSidebar: "Mostra nella barra laterale",
      +    hideFromSidebar: "Nascondi dalla barra laterale",
      +  },
      +
      +  skills: {
      +    title: "Competenze",
      +    searchPlaceholder: "Cerca competenze e toolset...",
      +    enabledOf: "{enabled}/{total} abilitati",
      +    all: "Tutti",
      +    categories: "Categorie",
      +    filters: "Filtri",
      +    noSkills: "Nessuna competenza trovata. Le competenze vengono caricate da ~/.hermes/skills/",
      +    noSkillsMatch: "Nessuna competenza corrisponde alla ricerca o al filtro.",
      +    skillCount: "{count} competenz{s}",
      +    resultCount: "{count} risultat{s}",
      +    noDescription: "Nessuna descrizione disponibile.",
      +    toolsets: "Toolset",
      +    toolsetLabel: "Toolset {name}",
      +    noToolsetsMatch: "Nessun toolset corrisponde alla ricerca.",
      +    setupNeeded: "Configurazione necessaria",
      +    disabledForCli: "Disabilitato per CLI",
      +    more: "+{count} in più",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Filtri",
      +    sections: "Sezioni",
      +    exportConfig: "Esporta configurazione come JSON",
      +    importConfig: "Importa configurazione da JSON",
      +    resetDefaults: "Ripristina predefiniti",
      +    resetScopeTooltip: "Ripristina {scope} ai valori predefiniti",
      +    confirmResetScope: "Ripristinare tutte le impostazioni di {scope} ai valori predefiniti? Questa operazione aggiorna solo il modulo — le modifiche non vengono scritte in config.yaml finché non premi Salva.",
      +    resetScopeToast: "{scope} ripristinato ai valori predefiniti — controlla e Salva per rendere persistente",
      +    rawYaml: "Configurazione YAML grezza",
      +    searchResults: "Risultati della ricerca",
      +    fields: "camp{s}",
      +    noFieldsMatch: 'Nessun campo corrisponde a "{query}"',
      +    configSaved: "Configurazione salvata",
      +    yamlConfigSaved: "Configurazione YAML salvata",
      +    failedToSave: "Salvataggio non riuscito",
      +    failedToSaveYaml: "Salvataggio YAML non riuscito",
      +    failedToLoadRaw: "Caricamento configurazione grezza non riuscito",
      +    configImported: "Configurazione importata — controlla e salva",
      +    invalidJson: "File JSON non valido",
      +    categories: {
      +      general: "Generale",
      +      agent: "Agente",
      +      terminal: "Terminale",
      +      display: "Visualizzazione",
      +      delegation: "Delega",
      +      memory: "Memoria",
      +      compression: "Compressione",
      +      security: "Sicurezza",
      +      browser: "Browser",
      +      voice: "Voce",
      +      tts: "Sintesi vocale",
      +      stt: "Riconoscimento vocale",
      +      logging: "Log",
      +      discord: "Discord",
      +      auxiliary: "Ausiliario",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "Le modifiche vengono salvate immediatamente su disco. Le sessioni attive rilevano automaticamente le nuove chiavi.",
      +    confirmClearMessage:
      +      "Il valore memorizzato per questa variabile sarà rimosso dal tuo file .env. Non può essere annullato dall'interfaccia.",
      +    confirmClearTitle: "Cancellare questa chiave?",
      +    description: "Gestisci chiavi API e segreti memorizzati in",
      +    hideAdvanced: "Nascondi avanzate",
      +    showAdvanced: "Mostra avanzate",
      +    llmProviders: "Provider LLM",
      +    providersConfigured: "{configured} di {total} provider configurati",
      +    getKey: "Ottieni chiave",
      +    notConfigured: "{count} non configurat{s}",
      +    notSet: "Non impostato",
      +    keysCount: "{count} chiav{s}",
      +    enterValue: "Inserisci valore...",
      +    replaceCurrentValue: "Sostituisci valore corrente ({preview})",
      +    showValue: "Mostra valore reale",
      +    hideValue: "Nascondi valore",
      +  },
      +
      +  oauth: {
      +    title: "Accessi provider (OAuth)",
      +    providerLogins: "Accessi provider (OAuth)",
      +    description: "{connected} di {total} provider OAuth connessi. I flussi di accesso vengono attualmente eseguiti tramite la CLI; clicca Copia comando e incolla in un terminale per configurare.",
      +    connected: "Connesso",
      +    expired: "Scaduto",
      +    notConnected: "Non connesso. Esegui {command} in un terminale.",
      +    runInTerminal: "in un terminale.",
      +    noProviders: "Nessun provider compatibile con OAuth rilevato.",
      +    login: "Accedi",
      +    disconnect: "Disconnetti",
      +    managedExternally: "Gestito esternamente",
      +    copied: "Copiato ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Copia comando CLI (per uso esterno / fallback)",
      +    connect: "Connetti",
      +    sessionExpires: "La sessione scade tra {time}",
      +    initiatingLogin: "Avvio del flusso di accesso…",
      +    exchangingCode: "Scambio del codice per i token…",
      +    connectedClosing: "Connesso! Chiusura…",
      +    loginFailed: "Accesso non riuscito.",
      +    sessionExpired: "Sessione scaduta. Clicca Riprova per iniziare un nuovo accesso.",
      +    reOpenAuth: "Riapri pagina di autenticazione",
      +    reOpenVerification: "Riapri pagina di verifica",
      +    submitCode: "Invia codice",
      +    pasteCode: "Incolla codice di autorizzazione (con suffisso #state va bene)",
      +    waitingAuth: "In attesa che tu autorizzi nel browser…",
      +    enterCodePrompt: "È stata aperta una nuova scheda. Inserisci questo codice se richiesto:",
      +    pkceStep1: "È stata aperta una nuova scheda su claude.ai. Accedi e clicca Autorizza.",
      +    pkceStep2: "Copia il codice di autorizzazione mostrato dopo l'autorizzazione.",
      +    pkceStep3: "Incollalo qui sotto e invia.",
      +    flowLabels: {
      +      pkce: "Accesso browser (PKCE)",
      +      device_code: "Codice dispositivo",
      +      external: "CLI esterna",
      +    },
      +    expiresIn: "scade tra {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Passa all'inglese",
      +  },
      +
      +  theme: {
      +    title: "Tema",
      +    switchTheme: "Cambia tema",
      +  },
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Badge Hermes da collezione, ottenuti dalla cronologia reale delle sessioni. Gli achievement noti non completati vengono mostrati come Scoperti; gli achievement segreti restano nascosti finché non compare il primo comportamento corrispondente.",
      +      scan_subtitle:
      +        "Scansione della cronologia delle sessioni Hermes in corso. La prima scansione può richiedere 5–10 secondi su cronologie ampie.",
      +    },
      +    actions: {
      +      rescan: "Riscansiona",
      +    },
      +    stats: {
      +      unlocked: "Sbloccati",
      +      unlocked_hint: "badge ottenuti",
      +      discovered: "Scoperti",
      +      discovered_hint: "noti, non ancora ottenuti",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] fino al primo segnale",
      +      highest_tier: "Livello più alto",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Più recente",
      +      latest_hint_empty: "usa Hermes di più",
      +      none_yet: "Nessuno ancora",
      +    },
      +    state: {
      +      unlocked: "Sbloccato",
      +      discovered: "Scoperto",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Obiettivo {tier}",
      +      hidden: "Nascosto",
      +      complete: "Completato",
      +      objective: "Obiettivo",
      +    },
      +    progress: {
      +      hidden: "nascosto",
      +    },
      +    scan: {
      +      building_headline: "Costruzione del profilo achievement…",
      +      building_detail:
      +        "Lettura di sessioni, chiamate agli strumenti, metadati del modello e stato di sblocco.",
      +      starting_headline: "Avvio della scansione achievement…",
      +      progress_detail:
      +        "Scansionate {scanned} di {total} sessioni · {pct}%. I badge si sbloccano man mano che viene elaborata altra cronologia.",
      +      idle_detail:
      +        "Lettura di sessioni, chiamate agli strumenti, metadati del modello e stato di sblocco. I badge appaiono qui non appena vengono sbloccati.",
      +    },
      +    guide: {
      +      tiers_header: "Livelli",
      +      secret_header: "[redacted:secret-assignment] segreti",
      +      secret_body:
      +        "I segreti nascondono il loro trigger esatto. Quando Hermes rileva un segnale correlato, la carta passa a Scoperto e mostra il requisito.",
      +      scan_status_header: "Stato della scansione",
      +      scan_status_body:
      +        "Hermes sta scansionando la cronologia locale una sola volta, poi le carte appariranno automaticamente. Non è bloccato nulla se richiede qualche secondo.",
      +      what_scanned_header: "Cosa viene scansionato",
      +      what_scanned_body:
      +        "Sessioni, chiamate agli strumenti, metadati del modello, errori, achievement e stato di sblocco locale.",
      +    },
      +    card: {
      +      share_title: "Condividi questo achievement",
      +      share_label: "Condividi {name}",
      +      share_text: "Condividi",
      +      how_to_reveal: "Come rivelarlo",
      +      what_counts: "Cosa conta",
      +      evidence_label: "Prova",
      +      evidence_session_fallback: "sessione",
      +      no_evidence: "Nessuna prova ancora",
      +    },
      +    latest: {
      +      header: "Sblocchi recenti",
      +    },
      +    empty: {
      +      no_secrets_header: "[redacted:secret-assignment] segreto nascosto rimasto in questa scansione.",
      +      no_secrets_body:
      +        "Indizio: i segreti di solito partono da fallimenti inusuali o pattern da utente esperto — conflitti di porte, muri di permessi, variabili d'ambiente mancanti, errori YAML, collisioni Docker, uso di rollback/checkpoint, cache hit o piccole correzioni dopo molto testo rosso.",
      +    },
      +    filters: {
      +      all_categories: "Tutti",
      +      visibility_all: "tutti",
      +      visibility_unlocked: "sbloccati",
      +      visibility_discovered: "scoperti",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Condividi achievement",
      +      header: "Condividi: {name}",
      +      close: "Chiudi",
      +      rendering: "Rendering…",
      +      card_alt: "Carta di condivisione {name}",
      +      error_generic: "Qualcosa è andato storto.",
      +      x_title: "Apre X con un post precompilato",
      +      x_button: "Condividi su X",
      +      copy_title: "Copia l'immagine per incollarla nel tuo post",
      +      copy_button: "Copia immagine",
      +      copied: "Copiato ✓",
      +      download_button: "Scarica PNG",
      +      hint:
      +        "Condividi su X apre un post precompilato in una nuova scheda. Clicca prima su Copia immagine se vuoi allegare il badge 1200×630 — X ti permette di incollarlo direttamente nell'editor del tweet. Scarica PNG salva il file per l'uso ovunque.",
      +      clipboard_unsupported:
      +        "La copia delle immagini negli appunti non è supportata in questo browser — usa Scarica invece.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Caricamento bacheca Kanban…",
      +    loadFailed: "Caricamento della bacheca Kanban non riuscito: ",
      +    loadFailedHint:
      +      "Il backend crea automaticamente kanban.db alla prima lettura. Se il problema persiste, controlla i log del dashboard.",
      +    board: "Bacheca",
      +    newBoard: "+ Nuova bacheca",
      +    newBoardTitle: "Nuova bacheca",
      +    newBoardDescription:
      +      "Le bacheche ti permettono di separare flussi di lavoro non correlati — una per progetto, repository o dominio. I worker su una bacheca non vedono mai le attività di un'altra.",
      +    slug: "Slug",
      +    slugHint: "— minuscolo, trattini, ad es. atm10-server",
      +    displayName: "Nome visualizzato",
      +    displayNameHint: "(facoltativo)",
      +    description: "Descrizione",
      +    descriptionHint: "(facoltativo)",
      +    icon: "Icona",
      +    iconHint: "(un singolo carattere o emoji)",
      +    switchAfterCreate: "Passa a questa bacheca dopo la creazione",
      +    cancel: "Annulla",
      +    creating: "Creazione…",
      +    createBoard: "Crea bacheca",
      +    search: "Cerca",
      +    filterCards: "Filtra schede…",
      +    tenant: "Tenant",
      +    allTenants: "Tutti i tenant",
      +    assignee: "Assegnatario",
      +    allProfiles: "Tutti i profili",
      +    showArchived: "Mostra archiviati",
      +    lanesByProfile: "Corsie per profilo",
      +    nudgeDispatcher: "Sollecita dispatcher",
      +    refresh: "Aggiorna",
      +    selected: "selezionato/i",
      +    complete: "Completa",
      +    archive: "Archivia",
      +    apply: "Applica",
      +    clear: "Cancella",
      +    createTask: "Crea attività in questa colonna",
      +    noTasks: "— nessuna attività —",
      +    unassigned: "non assegnato",
      +    untitled: "(senza titolo)",
      +    loadingDetail: "Caricamento…",
      +    addComment: "Aggiungi un commento… (Enter per inviare)",
      +    comment: "Commento",
      +    status: "Stato",
      +    workspace: "Workspace",
      +    skills: "Competenze",
      +    createdBy: "Creato da",
      +    result: "Result",
      +    comments: "Commenti",
      +    events: "Eventi",
      +    runHistory: "Cronologia esecuzioni",
      +    workerLog: "Log del worker",
      +    loadingLog: "Caricamento log…",
      +    noWorkerLog:
      +      "— nessun log del worker ancora (l'attività non è stata avviata o il log è stato ruotato) —",
      +    noDescription: "— nessuna descrizione —",
      +    noComments: "— nessun commento —",
      +    edit: "modifica",
      +    save: "Salva",
      +    dependencies: "Dipendenze",
      +    parents: "Padri:",
      +    children: "Figli:",
      +    none: "nessuno",
      +    addParent: "— aggiungi padre —",
      +    addChild: "— aggiungi figlio —",
      +    removeDependency: "Rimuovi dipendenza",
      +    block: "Blocca",
      +    unblock: "Sblocca",
      +    notifyHomeChannels: "Notifica i canali home",
      +    diagnostics: "Diagnostica",
      +    hide: "Nascondi",
      +    show: "Mostra",
      +    attention: "Attenzione",
      +    tasksNeedAttention: "attività richiedono attenzione",
      +    taskNeedsAttention: "1 attività richiede attenzione",
      +    diagnostic: "diagnostica",
      +    open: "Apri",
      +    close: "Chiudi (Esc)",
      +    reassignTo: "Riassegna a:",
      +    copied: "Copiato",
      +    copyCommand: "Copia comando negli appunti",
      +    reclaim: "Recupera",
      +    reassign: "Riassegna",
      +    renderingError: "La scheda Kanban ha avuto un errore di rendering",
      +    reloadView: "Ricarica vista",
      +    wsAuthFailed:
      +      "Autenticazione WebSocket non riuscita — ricarica la pagina per aggiornare il token di sessione.",
      +    markDone: "Contrassegnare {n} attività come completate?",
      +    markArchived: "Archiviare {n} attività?",
      +    warning: "Avviso",
      +    phantomIds: "ID fantasma:",
      +    active: "attivo",
      +    ended: "terminato",
      +    noProfile: "(nessun profilo)",
      +    showAllAttempts: "Mostra tutti i tentativi",
      +    sendingUpdates: "Invio aggiornamenti a",
      +    sendNotifications: "Invia notifiche di completed / blocked / gave_up a",
      +    archiveBoardConfirm:
      +      "Archiviare la bacheca '{name}'? Verrà spostata in boards/_archived/ in modo da poterla recuperare in seguito. Le attività di questa bacheca non appariranno più da nessuna parte nell'UI.",
      +    archiveBoardTitle: "Archivia questa bacheca",
      +    boardSwitcherHint: "Le bacheche ti permettono di separare flussi di lavoro non correlati",
      +    taskCreatedWarning: "Attività creata, ma: ",
      +    moveFailed: "Spostamento non riuscito: ",
      +    bulkFailed: "Massivo: ",
      +    completionBlockedHallucination: "⚠ Completamento bloccato — ID schede fantasma",
      +    suspectedHallucinatedReferences: "⚠ Il testo ha fatto riferimento a ID schede fantasma",
      +    pickProfileFirst: "Scegli prima un profilo.",
      +    unblockedMessage: "Sbloccato {id}. L'attività è pronta per il prossimo tick.",
      +    unblockFailed: "Sblocco non riuscito: ",
      +    reclaimedMessage: "Recuperato {id}. L'attività è di nuovo pronta.",
      +    reclaimFailed: "Recupero non riuscito: ",
      +    reassignedMessage: "Riassegnato {id} a {profile}.",
      +    reassignFailed: "Riassegnazione non riuscita: ",
      +    selectForBulk: "Seleziona per azioni massive",
      +    clickToEdit: "Clicca per modificare",
      +    clickToEditAssignee: "Clicca per modificare l'assegnatario",
      +    emptyAssignee: "(vuoto = rimuovi assegnazione)",
      +    columnLabels: {
      +      triage: "Triage",
      +      todo: "Da fare",
      +      ready: "Pronto",
      +      running: "In corso",
      +      blocked: "Bloccato",
      +      done: "Fatto",
      +      archived: "Archiviato",
      +    },
      +    columnHelp: {
      +      triage: "Idee grezze — un specifier elaborerà la specifica",
      +      todo: "In attesa di dipendenze o non assegnato",
      +      ready: "Assegnato e in attesa di un tick del dispatcher",
      +      running: "Preso in carico da un worker — in esecuzione",
      +      blocked: "Il worker ha richiesto input umano",
      +      done: "Completato",
      +      archived: "Archiviato",
      +    },
      +    confirmDone:
      +      "Contrassegnare questa attività come completata? La presa in carico del worker viene rilasciata e i figli dipendenti diventano pronti.",
      +    confirmArchive:
      +      "Archiviare questa attività? Sparirà dalla vista predefinita della bacheca.",
      +    confirmBlocked:
      +      "Contrassegnare questa attività come bloccata? La presa in carico del worker viene rilasciata.",
      +    completionSummary:
      +      "Riepilogo di completamento per {label}. Memorizzato come result dell'attività.",
      +    completionSummaryRequired:
      +      "Il riepilogo di completamento è obbligatorio prima di contrassegnare un'attività come completata.",
      +    triagePlaceholder: "Idea approssimativa — l'IA la specificherà…",
      +    taskTitlePlaceholder: "Titolo della nuova attività…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "assegnatario",
      +    priority: "Priorità",
      +    skillsPlaceholder:
      +      "competenze (facoltative, separate da virgole): translation, github-code-review",
      +    noParent: "— nessun padre —",
      +    workspacePathDir: "percorso del workspace (richiesto, ad es. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "percorso del workspace (facoltativo, derivato dall'assegnatario se vuoto)",
      +    logTruncated: "(mostrando ultimi 100 KB — log completo in ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/ja.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const ja: 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: "[redacted:secret-assignment]",
      +    totalSessions: "合計セッション数",
      +    apiCalls: "API 呼び出し",
      +    dailyTokenUsage: "[redacted:secret-assignment]",
      +    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: "プラグインを実行する前にこれらをキーに設定してください:",
      +    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} スキル{s}",
      +    resultCount: "{count} 件の結果{s}",
      +    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: "フィールド{s}",
      +    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} キー{s}",
      +    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: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment]",
      +      highest_tier: "最高ティア",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "最新",
      +      latest_hint_empty: "Hermes をもっと使ってみてください",
      +      none_yet: "まだありません",
      +    },
      +    state: {
      +      unlocked: "解除済み",
      +      discovered: "発見済み",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    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: "[redacted:secret-assignment]",
      +      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: "[redacted:secret-assignment]",
      +      no_secrets_body:
      +        "ヒント: シークレットは通常、想定外の失敗やパワーユーザー的なパターンから生まれます — ポート競合、権限の壁、環境変数の不足、YAML のミス、Docker の衝突、ロールバックやチェックポイントの利用、キャッシュヒット、あるいは大量の赤いエラーの後の小さな修正など。",
      +    },
      +    filters: {
      +      all_categories: "すべて",
      +      visibility_all: "すべて",
      +      visibility_unlocked: "解除済み",
      +      visibility_discovered: "発見済み",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    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:
      +      "ボードを使うと、関連のない作業の流れを分けられます — プロジェクト、リポジトリ、ドメインごとに 1 つずつ。あるボードのワーカーは、別のボードのタスクを見ることはありません。",
      +    slug: "スラッグ",
      +    slugHint: "— 小文字とハイフン、例: atm10-server",
      +    displayName: "表示名",
      +    displayNameHint: "(任意)",
      +    description: "説明",
      +    descriptionHint: "(任意)",
      +    icon: "アイコン",
      +    iconHint: "(1 文字または絵文字)",
      +    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: "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: ")",
      +  },
      +};
    • web/src/i18n/ko.ts
      @@ -0,0 +1,696 @@
      +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: ")",
      +  },
      +};
    • web/src/i18n/pt.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const pt: Translations = {
      +  common: {
      +    save: "Guardar",
      +    saving: "A guardar...",
      +    cancel: "Cancelar",
      +    close: "Fechar",
      +    confirm: "Confirmar",
      +    delete: "Eliminar",
      +    refresh: "Atualizar",
      +    retry: "Tentar novamente",
      +    search: "Pesquisar...",
      +    loading: "A carregar...",
      +    create: "Criar",
      +    creating: "A criar...",
      +    set: "Definir",
      +    replace: "Substituir",
      +    clear: "Limpar",
      +    live: "Ativo",
      +    off: "Desligado",
      +    enabled: "ativado",
      +    disabled: "desativado",
      +    active: "ativo",
      +    inactive: "inativo",
      +    unknown: "desconhecido",
      +    untitled: "Sem título",
      +    none: "Nenhum",
      +    form: "Formulário",
      +    noResults: "Sem resultados",
      +    of: "de",
      +    page: "Página",
      +    msgs: "msgs",
      +    tools: "ferramentas",
      +    match: "correspondência",
      +    other: "Outro",
      +    configured: "configurado",
      +    removed: "removido",
      +    failedToToggle: "Falha ao alternar",
      +    failedToRemove: "Falha ao remover",
      +    failedToReveal: "Falha ao revelar",
      +    collapse: "Recolher",
      +    expand: "Expandir",
      +    general: "Geral",
      +    messaging: "Mensagens",
      +    pluginLoadFailed:
      +      "Não foi possível carregar o script deste plugin. Verifique o separador Network (dashboard-plugins/…) e o caminho do plugin no servidor.",
      +    pluginNotRegistered:
      +      "O script do plugin não chamou register(), ou o script falhou. Abra a consola do browser para mais detalhes.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Fechar navegação",
      +    closeModelTools: "Fechar modelo e ferramentas",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Sessões ativas:",
      +    gatewayStatusLabel: "Estado do gateway:",
      +    gatewayStrip: {
      +      failed: "Falha ao iniciar",
      +      off: "Desligado",
      +      running: "A executar",
      +      starting: "A iniciar",
      +      stopped: "Parado",
      +    },
      +    nav: {
      +      analytics: "Análise",
      +      chat: "Chat",
      +      config: "Configuração",
      +      cron: "Cron",
      +      documentation: "Documentação",
      +      keys: "Chaves",
      +      logs: "Registos",
      +      models: "Modelos",
      +      profiles: "perfis: multiagentes",
      +      plugins: "Plugins",
      +      sessions: "Sessões",
      +      skills: "Competências",
      +    },
      +    modelToolsSheetSubtitle: "e ferramentas",
      +    modelToolsSheetTitle: "Modelo",
      +    navigation: "Navegação",
      +    openDocumentation: "Abrir documentação num novo separador",
      +    openNavigation: "Abrir navegação",
      +    pluginNavSection: "Plugins",
      +    sessionsActiveCount: "{count} ativa(s)",
      +    statusOverview: "Visão geral do estado",
      +    system: "Sistema",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "Ação falhou",
      +    actionFinished: "Concluído",
      +    actions: "Ações",
      +    agent: "Agente",
      +    activeSessions: "Sessões ativas",
      +    connected: "Ligado",
      +    connectedPlatforms: "Plataformas ligadas",
      +    disconnected: "Desligado",
      +    error: "Erro",
      +    failed: "Falhou",
      +    gateway: "Gateway",
      +    gatewayFailedToStart: "O gateway falhou ao iniciar",
      +    lastUpdate: "Última atualização",
      +    noneRunning: "Nenhum",
      +    notRunning: "Não está a executar",
      +    pid: "PID",
      +    platformDisconnected: "desligado",
      +    platformError: "erro",
      +    recentSessions: "Sessões recentes",
      +    restartGateway: "Reiniciar gateway",
      +    restartingGateway: "A reiniciar gateway…",
      +    running: "A executar",
      +    runningRemote: "A executar (remoto)",
      +    startFailed: "Falha ao iniciar",
      +    starting: "A iniciar",
      +    startedInBackground: "Iniciado em segundo plano — verifique os registos para acompanhar",
      +    stopped: "Parado",
      +    updateHermes: "Atualizar Hermes",
      +    updatingHermes: "A atualizar Hermes…",
      +    waitingForOutput: "À espera de saída…",
      +  },
      +
      +  sessions: {
      +    title: "Sessões",
      +    searchPlaceholder: "Pesquisar conteúdo das mensagens...",
      +    noSessions: "Ainda não há sessões",
      +    noMatch: "Nenhuma sessão corresponde à pesquisa",
      +    startConversation: "Inicie uma conversa para a ver aqui",
      +    noMessages: "Sem mensagens",
      +    untitledSession: "Sessão sem título",
      +    deleteSession: "Eliminar sessão",
      +    confirmDeleteTitle: "Eliminar sessão?",
      +    confirmDeleteMessage:
      +      "Esta ação remove permanentemente a conversa e todas as suas mensagens. Não é possível anular.",
      +    sessionDeleted: "Sessão eliminada",
      +    failedToDelete: "Falha ao eliminar a sessão",
      +    resumeInChat: "Retomar no Chat",
      +    previousPage: "Página anterior",
      +    nextPage: "Página seguinte",
      +    roles: {
      +      user: "Utilizador",
      +      assistant: "Assistente",
      +      system: "Sistema",
      +      tool: "Ferramenta",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Período:",
      +    totalTokens: "[redacted:secret-assignment] totais",
      +    totalSessions: "Sessões totais",
      +    apiCalls: "Chamadas à API",
      +    dailyTokenUsage: "[redacted:secret-assignment] diária de tokens",
      +    dailyBreakdown: "Detalhe diário",
      +    perModelBreakdown: "Detalhe por modelo",
      +    topSkills: "Competências principais",
      +    skill: "Competência",
      +    loads: "Carregadas pelo agente",
      +    edits: "Geridas pelo agente",
      +    lastUsed: "Última utilização",
      +    input: "Entrada",
      +    output: "Saída",
      +    total: "Total",
      +    noUsageData: "Sem dados de utilização para este período",
      +    startSession: "Inicie uma sessão para ver as análises aqui",
      +    date: "Data",
      +    model: "Modelo",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/dia (média)",
      +    acrossModels: "em {count} modelos",
      +    inOut: "{input} entrada / {output} saída",
      +  },
      +
      +  models: {
      +    modelsUsed: "Modelos utilizados",
      +    estimatedCost: "Custo est.",
      +    tokens: "[redacted:secret-assignment]",
      +    sessions: "sessões",
      +    avgPerSession: "média/sessão",
      +    apiCalls: "chamadas à API",
      +    toolCalls: "chamadas a ferramentas",
      +    noModelsData: "Sem dados de utilização de modelos para este período",
      +    startSession: "Inicie uma sessão para ver os dados de modelos aqui",
      +  },
      +
      +  logs: {
      +    title: "Registos",
      +    autoRefresh: "Atualização automática",
      +    file: "Ficheiro",
      +    level: "Nível",
      +    component: "Componente",
      +    lines: "Linhas",
      +    noLogLines: "Não foram encontradas linhas de registo",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Esta ação remove a tarefa do agendamento. Não é possível anular.",
      +    confirmDeleteTitle: "Eliminar tarefa agendada?",
      +    newJob: "Nova tarefa cron",
      +    nameOptional: "Nome (opcional)",
      +    namePlaceholder: "ex: Resumo diário",
      +    prompt: "Prompt",
      +    promptPlaceholder: "O que deve o agente fazer em cada execução?",
      +    schedule: "Agendamento (expressão cron)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Entregar a",
      +    scheduledJobs: "Tarefas agendadas",
      +    noJobs: "Sem tarefas cron configuradas. Crie uma acima.",
      +    last: "Última",
      +    next: "Próxima",
      +    pause: "Pausar",
      +    resume: "Retomar",
      +    triggerNow: "Acionar agora",
      +    delivery: {
      +      local: "Local",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Novo perfil",
      +    name: "Nome",
      +    namePlaceholder: "ex: coder, writer, etc.",
      +    nameRequired: "O nome é obrigatório",
      +    nameRule:
      +      "Apenas letras minúsculas, dígitos, _ e -; deve começar com letra ou dígito; até 64 caracteres.",
      +    invalidName: "Nome de perfil inválido",
      +    cloneFromDefault: "Clonar configuração do perfil predefinido",
      +    allProfiles: "Perfis",
      +    noProfiles: "Não foram encontrados perfis.",
      +    defaultBadge: "predefinido",
      +    hasEnv: "env",
      +    model: "Modelo",
      +    skills: "Competências",
      +    rename: "Renomear",
      +    editSoul: "Editar SOUL.md",
      +    soulSection: "SOUL.md (personalidade / prompt do sistema)",
      +    soulPlaceholder: "# Como este agente se deve comportar…",
      +    saveSoul: "Guardar SOUL",
      +    soulSaved: "SOUL.md guardado",
      +    openInTerminal: "Copiar comando da CLI",
      +    commandCopied: "Copiado para a área de transferência",
      +    copyFailed: "Não foi possível copiar",
      +    confirmDeleteTitle: "Eliminar perfil?",
      +    confirmDeleteMessage:
      +      "Esta ação elimina permanentemente o perfil '{name}' — configuração, chaves, memórias, sessões, competências, tarefas cron. Não é possível anular.",
      +    created: "Criado",
      +    deleted: "Eliminado",
      +    renamed: "Renomeado",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Motor de contexto",
      +    dashboardSlots: "Slots do dashboard",
      +    disableRuntime: "Desativar",
      +    enableAfterInstall: "Ativar após instalação",
      +    enableRuntime: "Ativar",
      +    forceReinstall: "Forçar reinstalação (eliminar pasta existente primeiro)",
      +    headline:
      +      "Descobrir, instalar, ativar e atualizar plugins Hermes (paridade com `hermes plugins`).",
      +    identifierLabel: "URL Git ou owner/repo",
      +    inactive: "inativo",
      +    installBtn: "Instalar a partir do Git",
      +    installHeading: "Instalar a partir de GitHub / URL Git",
      +    installHint: "Use a forma curta owner/repo ou um URL completo de clone https:// ou git@.",
      +    memoryProviderLabel: "Fornecedor de memória",
      +    missingEnvWarn: "Defina os seguintes em Chaves antes de o plugin poder executar:",
      +    noDashboardTab: "Sem separador no dashboard",
      +    openTab: "Abrir",
      +    orphanHeading: "Extensões só de dashboard (sem plugin.yaml de agente correspondente)",
      +    pluginListHeading: "Plugins instalados",
      +    providerDefaults: "incorporado / predefinido",
      +    providersHeading: "Plugins de fornecedor em runtime",
      +    providersHint:
      +      "Escreve memory.provider (vazio = incorporado) e context.engine no config.yaml. Aplicado na próxima sessão.",
      +    refreshDashboard: "Re-analisar extensões do dashboard",
      +    removeConfirm: "Remover este plugin de ~/.hermes/plugins/?",
      +    removeHint: "Apenas plugins instalados pelo utilizador em ~/.hermes/plugins podem ser removidos.",
      +    rescanHeading: "Registo de plugins SPA",
      +    rescanHint: "Re-analise depois de adicionar ficheiros em disco para que a barra lateral detete novos manifestos.",
      +    runtimeHeading: "Runtime do gateway (plugins YAML)",
      +    saveProviders: "Guardar definições do fornecedor",
      +    savedProviders: "Definições do fornecedor guardadas.",
      +    sourceBadge: "Fonte",
      +    authRequired: "Autenticação necessária",
      +    authRequiredHint: "Execute este comando para autenticar:",
      +    updateGit: "Git pull",
      +    versionBadge: "Versão",
      +    showInSidebar: "Mostrar na barra lateral",
      +    hideFromSidebar: "Ocultar da barra lateral",
      +  },
      +
      +  skills: {
      +    title: "Competências",
      +    searchPlaceholder: "Pesquisar competências e conjuntos de ferramentas...",
      +    enabledOf: "{enabled}/{total} ativadas",
      +    all: "Todas",
      +    categories: "Categorias",
      +    filters: "Filtros",
      +    noSkills: "Nenhuma competência encontrada. As competências são carregadas de ~/.hermes/skills/",
      +    noSkillsMatch: "Nenhuma competência corresponde à pesquisa ou filtro.",
      +    skillCount: "{count} competência{s}",
      +    resultCount: "{count} resultado{s}",
      +    noDescription: "Sem descrição disponível.",
      +    toolsets: "Conjuntos de ferramentas",
      +    toolsetLabel: "conjunto {name}",
      +    noToolsetsMatch: "Nenhum conjunto de ferramentas corresponde à pesquisa.",
      +    setupNeeded: "Configuração necessária",
      +    disabledForCli: "Desativado para CLI",
      +    more: "+{count} mais",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Filtros",
      +    sections: "Secções",
      +    exportConfig: "Exportar configuração como JSON",
      +    importConfig: "Importar configuração de JSON",
      +    resetDefaults: "Repor predefinições",
      +    resetScopeTooltip: "Repor {scope} para predefinições",
      +    confirmResetScope: "Repor todas as definições de {scope} para os valores predefinidos? Isto apenas atualiza o formulário — as alterações só são escritas em config.yaml quando premir Guardar.",
      +    resetScopeToast: "{scope} reposto para predefinições — reveja e Guarde para persistir",
      +    rawYaml: "Configuração YAML em bruto",
      +    searchResults: "Resultados da pesquisa",
      +    fields: "campo{s}",
      +    noFieldsMatch: 'Nenhum campo corresponde a "{query}"',
      +    configSaved: "Configuração guardada",
      +    yamlConfigSaved: "Configuração YAML guardada",
      +    failedToSave: "Falha ao guardar",
      +    failedToSaveYaml: "Falha ao guardar YAML",
      +    failedToLoadRaw: "Falha ao carregar configuração em bruto",
      +    configImported: "Configuração importada — reveja e guarde",
      +    invalidJson: "Ficheiro JSON inválido",
      +    categories: {
      +      general: "Geral",
      +      agent: "Agente",
      +      terminal: "Terminal",
      +      display: "Visualização",
      +      delegation: "Delegação",
      +      memory: "Memória",
      +      compression: "Compressão",
      +      security: "Segurança",
      +      browser: "Browser",
      +      voice: "Voz",
      +      tts: "Texto para fala",
      +      stt: "Fala para texto",
      +      logging: "Registo",
      +      discord: "Discord",
      +      auxiliary: "Auxiliar",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "As alterações são guardadas em disco imediatamente. As sessões ativas detetam novas chaves automaticamente.",
      +    confirmClearMessage:
      +      "O valor armazenado para esta variável será removido do seu ficheiro .env. Esta ação não pode ser anulada a partir da UI.",
      +    confirmClearTitle: "Limpar esta chave?",
      +    description: "Gerir chaves de API e segredos armazenados em",
      +    hideAdvanced: "Ocultar avançadas",
      +    showAdvanced: "Mostrar avançadas",
      +    llmProviders: "Fornecedores LLM",
      +    providersConfigured: "{configured} de {total} fornecedores configurados",
      +    getKey: "Obter chave",
      +    notConfigured: "{count} não configurado(s)",
      +    notSet: "Não definido",
      +    keysCount: "{count} chave{s}",
      +    enterValue: "Introduzir valor...",
      +    replaceCurrentValue: "Substituir valor atual ({preview})",
      +    showValue: "Mostrar valor real",
      +    hideValue: "Ocultar valor",
      +  },
      +
      +  oauth: {
      +    title: "Inícios de sessão de fornecedor (OAuth)",
      +    providerLogins: "Inícios de sessão de fornecedor (OAuth)",
      +    description: "{connected} de {total} fornecedores OAuth ligados. Os fluxos de início de sessão são executados via CLI; clique em Copiar comando e cole num terminal para configurar.",
      +    connected: "Ligado",
      +    expired: "Expirado",
      +    notConnected: "Não ligado. Execute {command} num terminal.",
      +    runInTerminal: "num terminal.",
      +    noProviders: "Não foram detetados fornecedores compatíveis com OAuth.",
      +    login: "Iniciar sessão",
      +    disconnect: "Desligar",
      +    managedExternally: "Gerido externamente",
      +    copied: "Copiado ✓",
      +    cli: "CLI",
      +    copyCliCommand: "Copiar comando CLI (para externo / fallback)",
      +    connect: "Ligar",
      +    sessionExpires: "A sessão expira em {time}",
      +    initiatingLogin: "A iniciar fluxo de início de sessão…",
      +    exchangingCode: "A trocar código por tokens…",
      +    connectedClosing: "Ligado! A fechar…",
      +    loginFailed: "Início de sessão falhou.",
      +    sessionExpired: "Sessão expirada. Clique em Tentar novamente para iniciar um novo início de sessão.",
      +    reOpenAuth: "Reabrir página de autenticação",
      +    reOpenVerification: "Reabrir página de verificação",
      +    submitCode: "Submeter código",
      +    pasteCode: "Cole o código de autorização (com sufixo #state também é válido)",
      +    waitingAuth: "À espera que autorize no browser…",
      +    enterCodePrompt: "Foi aberto um novo separador. Introduza este código se for solicitado:",
      +    pkceStep1: "Foi aberto um novo separador para claude.ai. Inicie sessão e clique em Authorize.",
      +    pkceStep2: "Copie o código de autorização mostrado após autorizar.",
      +    pkceStep3: "Cole-o abaixo e submeta.",
      +    flowLabels: {
      +      pkce: "Início de sessão pelo browser (PKCE)",
      +      device_code: "Código de dispositivo",
      +      external: "CLI externa",
      +    },
      +    expiresIn: "expira em {time}",
      +  },
      +
      +  language: {
      +    switchTo: "Mudar para inglês",
      +  },
      +
      +  theme: {
      +    title: "Tema",
      +    switchTheme: "Mudar tema",
      +  },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Distintivos colecionáveis do Hermes obtidos a partir do histórico real de sessões. Conquistas conhecidas mas ainda não obtidas aparecem como Descobertas; conquistas Secretas permanecem ocultas até surgir o primeiro comportamento correspondente.",
      +      scan_subtitle:
      +        "A analisar o histórico de sessões do Hermes. A primeira análise pode demorar 5–10 segundos em históricos extensos.",
      +    },
      +    actions: {
      +      rescan: "Voltar a analisar",
      +    },
      +    stats: {
      +      unlocked: "Desbloqueadas",
      +      unlocked_hint: "distintivos obtidos",
      +      discovered: "Descobertas",
      +      discovered_hint: "conhecidas, ainda não obtidas",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] até ao primeiro sinal",
      +      highest_tier: "Nível mais alto",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Mais recente",
      +      latest_hint_empty: "execute mais o Hermes",
      +      none_yet: "Ainda nenhuma",
      +    },
      +    state: {
      +      unlocked: "Desbloqueada",
      +      discovered: "Descoberta",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Objetivo {tier}",
      +      hidden: "Oculto",
      +      complete: "Completo",
      +      objective: "Objetivo",
      +    },
      +    progress: {
      +      hidden: "oculto",
      +    },
      +    scan: {
      +      building_headline: "A construir perfil de conquistas…",
      +      building_detail:
      +        "A ler sessões, chamadas de ferramentas, metadados de modelos e estado de desbloqueio.",
      +      starting_headline: "A iniciar análise de conquistas…",
      +      progress_detail:
      +        "Analisadas {scanned} de {total} sessões · {pct}%. Os distintivos são desbloqueados à medida que mais histórico é processado.",
      +      idle_detail:
      +        "A ler sessões, chamadas de ferramentas, metadados de modelos e estado de desbloqueio. Os distintivos aparecem aqui à medida que são desbloqueados.",
      +    },
      +    guide: {
      +      tiers_header: "Níveis",
      +      secret_header: "[redacted:secret-assignment] secretas",
      +      secret_body:
      +        "As secretas escondem o seu acionador exato. Assim que o Hermes detetar um sinal relacionado, o cartão passa a Descoberta e mostra o requisito.",
      +      scan_status_header: "Estado da análise",
      +      scan_status_body:
      +        "O Hermes analisa o histórico local uma vez e depois os cartões aparecem automaticamente. Nada está bloqueado se isto demorar alguns segundos.",
      +      what_scanned_header: "O que é analisado",
      +      what_scanned_body:
      +        "Sessões, chamadas de ferramentas, metadados de modelos, erros, conquistas e estado de desbloqueio local.",
      +    },
      +    card: {
      +      share_title: "Partilhar esta conquista",
      +      share_label: "Partilhar {name}",
      +      share_text: "Partilhar",
      +      how_to_reveal: "Como revelar",
      +      what_counts: "O que conta",
      +      evidence_label: "Evidência",
      +      evidence_session_fallback: "sessão",
      +      no_evidence: "Ainda sem evidência",
      +    },
      +    latest: {
      +      header: "Desbloqueios recentes",
      +    },
      +    empty: {
      +      no_secrets_header: "Não restam segredos ocultos nesta análise.",
      +      no_secrets_body:
      +        "Pista: as secretas começam normalmente em padrões pouco comuns de falha ou de utilizador avançado — conflitos de portas, barreiras de permissões, variáveis de ambiente em falta, erros de YAML, colisões de Docker, uso de rollback/checkpoint, acertos de cache ou pequenas correções após muito texto a vermelho.",
      +    },
      +    filters: {
      +      all_categories: "Todas",
      +      visibility_all: "todas",
      +      visibility_unlocked: "desbloqueadas",
      +      visibility_discovered: "descobertas",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    share: {
      +      dialog_label: "Partilhar conquista",
      +      header: "Partilhar: {name}",
      +      close: "Fechar",
      +      rendering: "A renderizar…",
      +      card_alt: "Cartão de partilha de {name}",
      +      error_generic: "Algo correu mal.",
      +      x_title: "Abre o X com uma publicação pré-preenchida",
      +      x_button: "Partilhar no X",
      +      copy_title: "Copiar a imagem para colar na sua publicação",
      +      copy_button: "Copiar imagem",
      +      copied: "Copiado ✓",
      +      download_button: "Transferir PNG",
      +      hint:
      +        "Partilhar no X abre uma publicação pré-preenchida num novo separador. Clique primeiro em Copiar imagem se quiser anexar o distintivo 1200×630 — o X permite colá-lo diretamente no compositor da publicação. Transferir PNG guarda o ficheiro para utilização em qualquer lado.",
      +      clipboard_unsupported:
      +        "A cópia de imagens para a área de transferência não é suportada neste navegador — utilize Transferir.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "A carregar o quadro Kanban…",
      +    loadFailed: "Falha ao carregar o quadro Kanban: ",
      +    loadFailedHint:
      +      "O backend cria automaticamente kanban.db na primeira leitura. Se persistir, consulte os registos do dashboard.",
      +    board: "Quadro",
      +    newBoard: "+ Novo quadro",
      +    newBoardTitle: "Novo quadro",
      +    newBoardDescription:
      +      "Os quadros permitem-lhe separar fluxos de trabalho não relacionados — um por projeto, repositório ou domínio. Os workers de um quadro nunca veem as tarefas de outro quadro.",
      +    slug: "Slug",
      +    slugHint: "— minúsculas, hífenes, p. ex. atm10-server",
      +    displayName: "Nome a apresentar",
      +    displayNameHint: "(opcional)",
      +    description: "Descrição",
      +    descriptionHint: "(opcional)",
      +    icon: "Ícone",
      +    iconHint: "(carácter único ou emoji)",
      +    switchAfterCreate: "Mudar para este quadro após o criar",
      +    cancel: "Cancelar",
      +    creating: "A criar…",
      +    createBoard: "Criar quadro",
      +    search: "Pesquisar",
      +    filterCards: "Filtrar cartões…",
      +    tenant: "Tenant",
      +    allTenants: "Todos os tenants",
      +    assignee: "Responsável",
      +    allProfiles: "Todos os perfis",
      +    showArchived: "Mostrar arquivados",
      +    lanesByProfile: "Faixas por perfil",
      +    nudgeDispatcher: "Despertar o dispatcher",
      +    refresh: "Atualizar",
      +    selected: "selecionado(s)",
      +    complete: "Concluir",
      +    archive: "Arquivar",
      +    apply: "Aplicar",
      +    clear: "Limpar",
      +    createTask: "Criar tarefa nesta coluna",
      +    noTasks: "— sem tarefas —",
      +    unassigned: "sem atribuição",
      +    untitled: "(sem título)",
      +    loadingDetail: "A carregar…",
      +    addComment: "Adicionar um comentário… (Enter para submeter)",
      +    comment: "Comentário",
      +    status: "Estado",
      +    workspace: "Espaço de trabalho",
      +    skills: "Competências",
      +    createdBy: "Criado por",
      +    result: "Resultado",
      +    comments: "Comentários",
      +    events: "Eventos",
      +    runHistory: "Histórico de execuções",
      +    workerLog: "Registo do worker",
      +    loadingLog: "A carregar registo…",
      +    noWorkerLog:
      +      "— ainda não há registo do worker (a tarefa não foi iniciada ou o registo foi rotacionado) —",
      +    noDescription: "— sem descrição —",
      +    noComments: "— sem comentários —",
      +    edit: "editar",
      +    save: "Guardar",
      +    dependencies: "Dependências",
      +    parents: "Pais:",
      +    children: "Filhos:",
      +    none: "nenhum",
      +    addParent: "— adicionar pai —",
      +    addChild: "— adicionar filho —",
      +    removeDependency: "Remover dependência",
      +    block: "Bloquear",
      +    unblock: "Desbloquear",
      +    notifyHomeChannels: "Notificar canais principais",
      +    diagnostics: "Diagnósticos",
      +    hide: "Ocultar",
      +    show: "Mostrar",
      +    attention: "Atenção",
      +    tasksNeedAttention: "tarefas precisam de atenção",
      +    taskNeedsAttention: "1 tarefa precisa de atenção",
      +    diagnostic: "diagnóstico",
      +    open: "Abrir",
      +    close: "Fechar (Esc)",
      +    reassignTo: "Reatribuir a:",
      +    copied: "Copiado",
      +    copyCommand: "Copiar comando para a área de transferência",
      +    reclaim: "Reivindicar",
      +    reassign: "Reatribuir",
      +    renderingError: "O separador Kanban encontrou um erro de renderização",
      +    reloadView: "Recarregar vista",
      +    wsAuthFailed:
      +      "Falha de autenticação WebSocket — recarregue a página para atualizar o token de sessão.",
      +    markDone: "Marcar {n} tarefa(s) como concluídas?",
      +    markArchived: "Arquivar {n} tarefa(s)?",
      +    warning: "Aviso",
      +    phantomIds: "Ids fantasma:",
      +    active: "ativo",
      +    ended: "terminado",
      +    noProfile: "(sem perfil)",
      +    showAllAttempts: "Mostrar todas as tentativas",
      +    sendingUpdates: "A enviar atualizações para",
      +    sendNotifications: "Enviar notificações de completed / blocked / gave_up para",
      +    archiveBoardConfirm:
      +      "Arquivar o quadro '{name}'? Será movido para boards/_archived/ para que possa recuperá-lo mais tarde. As tarefas deste quadro deixarão de aparecer em qualquer parte da interface.",
      +    archiveBoardTitle: "Arquivar este quadro",
      +    boardSwitcherHint: "Os quadros permitem-lhe separar fluxos de trabalho não relacionados",
      +    taskCreatedWarning: "Tarefa criada, mas: ",
      +    moveFailed: "Falha ao mover: ",
      +    bulkFailed: "Em lote: ",
      +    completionBlockedHallucination: "⚠ Conclusão bloqueada — ids de cartões fantasma",
      +    suspectedHallucinatedReferences: "⚠ O texto referenciou ids de cartões fantasma",
      +    pickProfileFirst: "Escolha primeiro um perfil.",
      +    unblockedMessage: "{id} desbloqueado. A tarefa está pronta para o próximo tick.",
      +    unblockFailed: "Falha ao desbloquear: ",
      +    reclaimedMessage: "{id} reivindicado. A tarefa voltou a ready.",
      +    reclaimFailed: "Falha ao reivindicar: ",
      +    reassignedMessage: "{id} reatribuído a {profile}.",
      +    reassignFailed: "Falha ao reatribuir: ",
      +    selectForBulk: "Selecionar para ações em lote",
      +    clickToEdit: "Clique para editar",
      +    clickToEditAssignee: "Clique para editar responsável",
      +    emptyAssignee: "(vazio = remover atribuição)",
      +    columnLabels: {
      +      triage: "Triagem",
      +      todo: "A fazer",
      +      ready: "Pronto",
      +      running: "Em curso",
      +      blocked: "Bloqueado",
      +      done: "Concluído",
      +      archived: "Arquivado",
      +    },
      +    columnHelp: {
      +      triage: "Ideias em bruto — um specifier vai detalhar a especificação",
      +      todo: "À espera de dependências ou sem atribuição",
      +      ready: "Atribuído e à espera de um tick do dispatcher",
      +      running: "Reivindicado por um worker — em execução",
      +      blocked: "O worker pediu intervenção humana",
      +      done: "Concluído",
      +      archived: "Arquivado",
      +    },
      +    confirmDone:
      +      "Marcar esta tarefa como concluída? A reivindicação do worker é libertada e os filhos dependentes ficam prontos.",
      +    confirmArchive:
      +      "Arquivar esta tarefa? Desaparece da vista padrão do quadro.",
      +    confirmBlocked:
      +      "Marcar esta tarefa como bloqueada? A reivindicação do worker é libertada.",
      +    completionSummary:
      +      "Resumo de conclusão para {label}. Será guardado como o resultado da tarefa.",
      +    completionSummaryRequired:
      +      "É necessário um resumo de conclusão antes de marcar uma tarefa como concluída.",
      +    triagePlaceholder: "Ideia aproximada — a IA irá especificá-la…",
      +    taskTitlePlaceholder: "Título da nova tarefa…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "responsável",
      +    priority: "Prioridade",
      +    skillsPlaceholder:
      +      "competências (opcional, separadas por vírgulas): translation, github-code-review",
      +    noParent: "— sem pai —",
      +    workspacePathDir: "caminho do espaço de trabalho (obrigatório, p. ex. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "caminho do espaço de trabalho (opcional, derivado do responsável se vazio)",
      +    logTruncated: "(a mostrar os últimos 100 KB — registo completo em ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/ru.ts
      @@ -0,0 +1,696 @@
      +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:
      +      "Не удалось загрузить скрипт этого плагина. Проверьте вкладку «Сеть» (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: "[redacted:secret-assignment] токенов по дням",
      +    dailyBreakdown: "Разбивка по дням",
      +    perModelBreakdown: "Разбивка по моделям",
      +    topSkills: "Популярные навыки",
      +    skill: "Навык",
      +    loads: "Загружено агентом",
      +    edits: "Управляется агентом",
      +    lastUsed: "Последнее использование",
      +    input: "Ввод",
      +    output: "Вывод",
      +    total: "Итого",
      +    noUsageData: "Нет данных об использовании за этот период",
      +    startSession: "Начните сессию, чтобы увидеть аналитику",
      +    date: "Дата",
      +    model: "Модель",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/день в среднем",
      +    acrossModels: "по {count} моделям",
      +    inOut: "{input} вход / {output} выход",
      +  },
      +
      +  models: {
      +    modelsUsed: "Использовано моделей",
      +    estimatedCost: "Оценка стоимости",
      +    tokens: "[redacted:secret-assignment]",
      +    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: "Задайте эти переменные в разделе «Ключи», прежде чем плагин сможет работать:",
      +    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} навык{s}",
      +    resultCount: "{count} результат{s}",
      +    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: "пол{s}",
      +    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} ключ{s}",
      +    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, полученные на основе реальной истории сессий. Известные, но ещё не полученные достижения отображаются как «Обнаруженные»; «Секретные» достижения остаются скрытыми до появления первого подходящего поведения.",
      +      scan_subtitle:
      +        "Анализ истории сессий Hermes. Первое сканирование может занять 5–10 секунд при большой истории.",
      +    },
      +    actions: {
      +      rescan: "Пересканировать",
      +    },
      +    stats: {
      +      unlocked: "Разблокировано",
      +      unlocked_hint: "полученные значки",
      +      discovered: "Обнаружено",
      +      discovered_hint: "известные, ещё не получены",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] до первого сигнала",
      +      highest_tier: "Высший уровень",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Последнее",
      +      latest_hint_empty: "запускайте Hermes чаще",
      +      none_yet: "Пока нет",
      +    },
      +    state: {
      +      unlocked: "Разблокировано",
      +      discovered: "Обнаружено",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Цель: {tier}",
      +      hidden: "Скрыто",
      +      complete: "Завершено",
      +      objective: "Задача",
      +    },
      +    progress: {
      +      hidden: "скрыто",
      +    },
      +    scan: {
      +      building_headline: "Создание профиля достижений…",
      +      building_detail:
      +        "Чтение сессий, вызовов инструментов, метаданных моделей и состояния разблокировки.",
      +      starting_headline: "Запуск сканирования достижений…",
      +      progress_detail:
      +        "Просканировано {scanned} из {total} сессий · {pct}%. Значки разблокируются по мере поступления истории.",
      +      idle_detail:
      +        "Чтение сессий, вызовов инструментов, метаданных моделей и состояния разблокировки. Значки появляются здесь по мере разблокировки.",
      +    },
      +    guide: {
      +      tiers_header: "Уровни",
      +      secret_header: "[redacted:secret-assignment] достижения",
      +      secret_body:
      +        "Секретные достижения скрывают свой точный триггер. Как только Hermes обнаруживает связанный сигнал, карточка становится «Обнаруженной» и показывает требование.",
      +      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, использование rollback/checkpoint, попадания в кеш или мелкие исправления после большого количества красного текста.",
      +    },
      +    filters: {
      +      all_categories: "Все",
      +      visibility_all: "все",
      +      visibility_unlocked: "разблокированные",
      +      visibility_discovered: "обнаруженные",
      +      visibility_secret: "[redacted:secret-assignment]",
      +    },
      +    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: "Slug",
      +    slugHint: "— строчные буквы, дефисы, например atm10-server",
      +    displayName: "Отображаемое имя",
      +    displayNameHint: "(необязательно)",
      +    description: "Описание",
      +    descriptionHint: "(необязательно)",
      +    icon: "Значок",
      +    iconHint: "(один символ или эмодзи)",
      +    switchAfterCreate: "Переключиться на эту доску после создания",
      +    cancel: "Отмена",
      +    creating: "Создание…",
      +    createBoard: "Создать доску",
      +    search: "Поиск",
      +    filterCards: "Фильтр карточек…",
      +    tenant: "Tenant",
      +    allTenants: "Все tenant'ы",
      +    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: "Отправлять уведомления completed / blocked / gave_up в",
      +    archiveBoardConfirm:
      +      "Архивировать доску '{name}'? Она будет перемещена в boards/_archived/, чтобы её можно было восстановить позже. Задачи этой доски больше не будут отображаться нигде в интерфейсе.",
      +    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: "Сырые идеи — specifier подготовит спецификацию",
      +      todo: "Ожидает зависимостей или без исполнителя",
      +      ready: "Назначено и ждёт тика диспетчера",
      +      running: "Взято воркером — выполняется",
      +      blocked: "Воркер запросил вмешательство человека",
      +      done: "Завершено",
      +      archived: "В архиве",
      +    },
      +    confirmDone:
      +      "Отметить эту задачу как выполненную? Захват воркера будет освобождён, а зависимые потомки станут готовыми.",
      +    confirmArchive:
      +      "Архивировать эту задачу? Она исчезнет из стандартного вида доски.",
      +    confirmBlocked:
      +      "Отметить эту задачу как заблокированную? Захват воркера будет освобождён.",
      +    completionSummary:
      +      "Сводка завершения для {label}. Сохраняется как результат задачи.",
      +    completionSummaryRequired:
      +      "Перед отметкой задачи как выполненной требуется сводка завершения.",
      +    triagePlaceholder: "Черновая идея — ИИ её проспецифицирует…",
      +    taskTitlePlaceholder: "Название новой задачи…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "исполнитель",
      +    priority: "Приоритет",
      +    skillsPlaceholder:
      +      "навыки (необязательно, через запятую): translation, github-code-review",
      +    noParent: "— без родителя —",
      +    workspacePathDir: "путь к рабочей области (обязательно, например ~/projects/my-app)",
      +    workspacePathOptional:
      +      "путь к рабочей области (необязательно, выводится из исполнителя, если не указан)",
      +    logTruncated: "(показаны последние 100 KB — полный журнал в ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/tr.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const tr: Translations = {
      +  common: {
      +    save: "Kaydet",
      +    saving: "Kaydediliyor...",
      +    cancel: "İptal",
      +    close: "Kapat",
      +    confirm: "Onayla",
      +    delete: "Sil",
      +    refresh: "Yenile",
      +    retry: "Yeniden dene",
      +    search: "Ara...",
      +    loading: "Yükleniyor...",
      +    create: "Oluştur",
      +    creating: "Oluşturuluyor...",
      +    set: "Ayarla",
      +    replace: "Değiştir",
      +    clear: "Temizle",
      +    live: "Canlı",
      +    off: "Kapalı",
      +    enabled: "etkin",
      +    disabled: "devre dışı",
      +    active: "aktif",
      +    inactive: "pasif",
      +    unknown: "bilinmiyor",
      +    untitled: "Başlıksız",
      +    none: "Yok",
      +    form: "Form",
      +    noResults: "Sonuç yok",
      +    of: "/",
      +    page: "Sayfa",
      +    msgs: "mesaj",
      +    tools: "araçlar",
      +    match: "eşleşme",
      +    other: "Diğer",
      +    configured: "yapılandırıldı",
      +    removed: "kaldırıldı",
      +    failedToToggle: "Değiştirilemedi",
      +    failedToRemove: "Kaldırılamadı",
      +    failedToReveal: "Gösterilemedi",
      +    collapse: "Daralt",
      +    expand: "Genişlet",
      +    general: "Genel",
      +    messaging: "Mesajlaşma",
      +    pluginLoadFailed:
      +      "Bu eklentinin betiği yüklenemedi. Ağ sekmesini (dashboard-plugins/…) ve sunucunun eklenti yolunu kontrol edin.",
      +    pluginNotRegistered:
      +      "Eklenti betiği register() çağırmadı veya betik hata verdi. Ayrıntılar için tarayıcı konsolunu açın.",
      +  },
      +
      +  app: {
      +    brand: "Hermes Agent",
      +    brandShort: "HA",
      +    closeNavigation: "Gezintiyi kapat",
      +    closeModelTools: "Modeli ve araçları kapat",
      +    footer: {
      +      org: "Nous Research",
      +    },
      +    activeSessionsLabel: "Aktif Oturumlar:",
      +    gatewayStatusLabel: "Ağ Geçidi Durumu:",
      +    gatewayStrip: {
      +      failed: "Başlatma başarısız",
      +      off: "Kapalı",
      +      running: "Çalışıyor",
      +      starting: "Başlatılıyor",
      +      stopped: "Durduruldu",
      +    },
      +    nav: {
      +      analytics: "Analiz",
      +      chat: "Sohbet",
      +      config: "Yapılandırma",
      +      cron: "Cron",
      +      documentation: "Dokümantasyon",
      +      keys: "Anahtarlar",
      +      logs: "Günlükler",
      +      models: "Modeller",
      +      profiles: "profiller : çoklu agent",
      +      plugins: "Eklentiler",
      +      sessions: "Oturumlar",
      +      skills: "Yetenekler",
      +    },
      +    modelToolsSheetSubtitle: "& araçlar",
      +    modelToolsSheetTitle: "Model",
      +    navigation: "Gezinti",
      +    openDocumentation: "Dokümantasyonu yeni sekmede aç",
      +    openNavigation: "Gezintiyi aç",
      +    pluginNavSection: "Eklentiler",
      +    sessionsActiveCount: "{count} aktif",
      +    statusOverview: "Durum özeti",
      +    system: "Sistem",
      +    webUi: "Web UI",
      +  },
      +
      +  status: {
      +    actionFailed: "İşlem başarısız",
      +    actionFinished: "Tamamlandı",
      +    actions: "İşlemler",
      +    agent: "Agent",
      +    activeSessions: "Aktif Oturumlar",
      +    connected: "Bağlandı",
      +    connectedPlatforms: "Bağlı Platformlar",
      +    disconnected: "Bağlantı kesildi",
      +    error: "Hata",
      +    failed: "Başarısız",
      +    gateway: "Ağ Geçidi",
      +    gatewayFailedToStart: "Ağ geçidi başlatılamadı",
      +    lastUpdate: "Son güncelleme",
      +    noneRunning: "Yok",
      +    notRunning: "Çalışmıyor",
      +    pid: "PID",
      +    platformDisconnected: "bağlantı kesildi",
      +    platformError: "hata",
      +    recentSessions: "Son Oturumlar",
      +    restartGateway: "Ağ Geçidini Yeniden Başlat",
      +    restartingGateway: "Ağ geçidi yeniden başlatılıyor…",
      +    running: "Çalışıyor",
      +    runningRemote: "Çalışıyor (uzak)",
      +    startFailed: "Başlatma başarısız",
      +    starting: "Başlatılıyor",
      +    startedInBackground: "Arka planda başlatıldı — ilerleme için günlüklere bakın",
      +    stopped: "Durduruldu",
      +    updateHermes: "Hermes'i Güncelle",
      +    updatingHermes: "Hermes güncelleniyor…",
      +    waitingForOutput: "Çıktı bekleniyor…",
      +  },
      +
      +  sessions: {
      +    title: "Oturumlar",
      +    searchPlaceholder: "Mesaj içeriğinde ara...",
      +    noSessions: "Henüz oturum yok",
      +    noMatch: "Aramanızla eşleşen oturum yok",
      +    startConversation: "Burada görmek için bir konuşma başlatın",
      +    noMessages: "Mesaj yok",
      +    untitledSession: "Başlıksız oturum",
      +    deleteSession: "Oturumu sil",
      +    confirmDeleteTitle: "Oturum silinsin mi?",
      +    confirmDeleteMessage:
      +      "Bu, konuşmayı ve tüm mesajlarını kalıcı olarak siler. Bu işlem geri alınamaz.",
      +    sessionDeleted: "Oturum silindi",
      +    failedToDelete: "Oturum silinemedi",
      +    resumeInChat: "Sohbette Devam Et",
      +    previousPage: "Önceki sayfa",
      +    nextPage: "Sonraki sayfa",
      +    roles: {
      +      user: "Kullanıcı",
      +      assistant: "Asistan",
      +      system: "Sistem",
      +      tool: "Araç",
      +    },
      +  },
      +
      +  analytics: {
      +    period: "Dönem:",
      +    totalTokens: "[redacted:secret-assignment] Token",
      +    totalSessions: "Toplam Oturum",
      +    apiCalls: "API Çağrıları",
      +    dailyTokenUsage: "[redacted:secret-assignment] Token Kullanımı",
      +    dailyBreakdown: "Günlük Dağılım",
      +    perModelBreakdown: "Model Bazında Dağılım",
      +    topSkills: "En Çok Kullanılan Yetenekler",
      +    skill: "Yetenek",
      +    loads: "Agent Yüklendi",
      +    edits: "Agent Yönetildi",
      +    lastUsed: "Son Kullanım",
      +    input: "Giriş",
      +    output: "Çıkış",
      +    total: "Toplam",
      +    noUsageData: "Bu dönem için kullanım verisi yok",
      +    startSession: "Burada analizleri görmek için bir oturum başlatın",
      +    date: "Tarih",
      +    model: "Model",
      +    tokens: "Token",
      +    perDayAvg: "/gün ort",
      +    acrossModels: "{count} model üzerinden",
      +    inOut: "{input} giriş / {output} çıkış",
      +  },
      +
      +  models: {
      +    modelsUsed: "Kullanılan Modeller",
      +    estimatedCost: "Tahmini Maliyet",
      +    tokens: "token",
      +    sessions: "oturum",
      +    avgPerSession: "ort/oturum",
      +    apiCalls: "API çağrıları",
      +    toolCalls: "araç çağrıları",
      +    noModelsData: "Bu dönem için model kullanım verisi yok",
      +    startSession: "Burada model verilerini görmek için bir oturum başlatın",
      +  },
      +
      +  logs: {
      +    title: "Günlükler",
      +    autoRefresh: "Otomatik yenile",
      +    file: "Dosya",
      +    level: "Seviye",
      +    component: "Bileşen",
      +    lines: "Satırlar",
      +    noLogLines: "Günlük satırı bulunamadı",
      +  },
      +
      +  cron: {
      +    confirmDeleteMessage:
      +      "Bu, görevi zamanlamadan kaldırır. Bu işlem geri alınamaz.",
      +    confirmDeleteTitle: "Zamanlanmış görev silinsin mi?",
      +    newJob: "Yeni Cron Görevi",
      +    nameOptional: "Ad (isteğe bağlı)",
      +    namePlaceholder: "örn. Günlük özet",
      +    prompt: "İstem",
      +    promptPlaceholder: "Agent her çalıştırmada ne yapmalı?",
      +    schedule: "Zamanlama (cron ifadesi)",
      +    schedulePlaceholder: "0 9 * * *",
      +    deliverTo: "Şuraya teslim et",
      +    scheduledJobs: "Zamanlanmış Görevler",
      +    noJobs: "Yapılandırılmış cron görevi yok. Yukarıdan bir tane oluşturun.",
      +    last: "Son",
      +    next: "Sonraki",
      +    pause: "Duraklat",
      +    resume: "Devam ettir",
      +    triggerNow: "Şimdi tetikle",
      +    delivery: {
      +      local: "Yerel",
      +      telegram: "Telegram",
      +      discord: "Discord",
      +      slack: "Slack",
      +      email: "Email",
      +    },
      +  },
      +
      +  profiles: {
      +    newProfile: "Yeni Profil",
      +    name: "Ad",
      +    namePlaceholder: "örn. coder, writer, vb.",
      +    nameRequired: "Ad gereklidir",
      +    nameRule:
      +      "Yalnızca küçük harfler, rakamlar, _ ve - kullanılabilir; harf veya rakamla başlamalı; en fazla 64 karakter.",
      +    invalidName: "Geçersiz profil adı",
      +    cloneFromDefault: "Varsayılan profilden yapılandırmayı klonla",
      +    allProfiles: "Profiller",
      +    noProfiles: "Profil bulunamadı.",
      +    defaultBadge: "varsayılan",
      +    hasEnv: "env",
      +    model: "Model",
      +    skills: "Yetenekler",
      +    rename: "Yeniden adlandır",
      +    editSoul: "SOUL.md'yi düzenle",
      +    soulSection: "SOUL.md (kişilik / sistem istemi)",
      +    soulPlaceholder: "# Bu agent nasıl davranmalı…",
      +    saveSoul: "SOUL'u kaydet",
      +    soulSaved: "SOUL.md kaydedildi",
      +    openInTerminal: "CLI komutunu kopyala",
      +    commandCopied: "Panoya kopyalandı",
      +    copyFailed: "Kopyalanamadı",
      +    confirmDeleteTitle: "Profil silinsin mi?",
      +    confirmDeleteMessage:
      +      "Bu, '{name}' profilini kalıcı olarak siler — yapılandırma, anahtarlar, hatıralar, oturumlar, yetenekler, cron görevleri. Geri alınamaz.",
      +    created: "Oluşturuldu",
      +    deleted: "Silindi",
      +    renamed: "Yeniden adlandırıldı",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "Bağlam motoru",
      +    dashboardSlots: "Pano yuvaları",
      +    disableRuntime: "Devre dışı bırak",
      +    enableAfterInstall: "Yüklemeden sonra etkinleştir",
      +    enableRuntime: "Etkinleştir",
      +    forceReinstall: "Yeniden yüklemeyi zorla (önce mevcut klasörü sil)",
      +    headline:
      +      "Hermes eklentilerini keşfedin, yükleyin, etkinleştirin ve güncelleyin (`hermes plugins` ile eşdeğer).",
      +    identifierLabel: "Git URL veya owner/repo",
      +    inactive: "pasif",
      +    installBtn: "Git'ten yükle",
      +    installHeading: "GitHub / Git URL'sinden yükle",
      +    installHint: "owner/repo kısayolunu veya tam https:// ya da git@ klon URL'sini kullanın.",
      +    memoryProviderLabel: "Bellek sağlayıcısı",
      +    missingEnvWarn: "Eklenti çalışmadan önce bunları Anahtarlar bölümünde ayarlayın:",
      +    noDashboardTab: "Pano sekmesi yok",
      +    openTab: "Aç",
      +    orphanHeading: "Yalnızca pano uzantıları (eşleşen agent plugin.yaml yok)",
      +    pluginListHeading: "Yüklü eklentiler",
      +    providerDefaults: "yerleşik / varsayılan",
      +    providersHeading: "Çalışma zamanı sağlayıcı eklentileri",
      +    providersHint:
      +      "config.yaml'a memory.provider (boş = yerleşik) ve context.engine yazar. Bir sonraki oturumda etkili olur.",
      +    refreshDashboard: "Pano uzantılarını yeniden tara",
      +    removeConfirm: "Bu eklenti ~/.hermes/plugins/ içinden kaldırılsın mı?",
      +    removeHint: "Yalnızca ~/.hermes/plugins altındaki kullanıcı tarafından yüklenmiş eklentiler kaldırılabilir.",
      +    rescanHeading: "SPA eklenti kayıt defteri",
      +    rescanHint: "Diske dosya ekledikten sonra yeniden tarayın, böylece pano kenar çubuğu yeni manifestleri algılar.",
      +    runtimeHeading: "Ağ geçidi çalışma zamanı (YAML eklentileri)",
      +    saveProviders: "Sağlayıcı ayarlarını kaydet",
      +    savedProviders: "Sağlayıcı ayarları kaydedildi.",
      +    sourceBadge: "Kaynak",
      +    authRequired: "Kimlik doğrulama gerekli",
      +    authRequiredHint: "Kimlik doğrulamak için bu komutu çalıştırın:",
      +    updateGit: "Git pull",
      +    versionBadge: "Sürüm",
      +    showInSidebar: "Kenar çubuğunda göster",
      +    hideFromSidebar: "Kenar çubuğundan gizle",
      +  },
      +
      +  skills: {
      +    title: "Yetenekler",
      +    searchPlaceholder: "Yetenek ve araç setlerinde ara...",
      +    enabledOf: "{enabled}/{total} etkin",
      +    all: "Tümü",
      +    categories: "Kategoriler",
      +    filters: "Filtreler",
      +    noSkills: "Yetenek bulunamadı. Yetenekler ~/.hermes/skills/ adresinden yüklenir",
      +    noSkillsMatch: "Aramanız veya filtrenizle eşleşen yetenek yok.",
      +    skillCount: "{count} yetenek{s}",
      +    resultCount: "{count} sonuç{s}",
      +    noDescription: "Açıklama mevcut değil.",
      +    toolsets: "Araç setleri",
      +    toolsetLabel: "{name} araç seti",
      +    noToolsetsMatch: "Aramayla eşleşen araç seti yok.",
      +    setupNeeded: "Kurulum gerekli",
      +    disabledForCli: "CLI için devre dışı",
      +    more: "+{count} daha",
      +  },
      +
      +  config: {
      +    configPath: "~/.hermes/config.yaml",
      +    filters: "Filtreler",
      +    sections: "Bölümler",
      +    exportConfig: "Yapılandırmayı JSON olarak dışa aktar",
      +    importConfig: "Yapılandırmayı JSON'dan içe aktar",
      +    resetDefaults: "Varsayılanlara sıfırla",
      +    resetScopeTooltip: "{scope} varsayılanlara sıfırla",
      +    confirmResetScope: "{scope} ayarlarının tümü varsayılanlara sıfırlansın mı? Bu yalnızca formu günceller — değişiklikler Kaydet'e basılana kadar config.yaml'a yazılmaz.",
      +    resetScopeToast: "{scope} varsayılanlara sıfırlandı — gözden geçirip kalıcı kılmak için Kaydet'e basın",
      +    rawYaml: "Ham YAML Yapılandırması",
      +    searchResults: "Arama Sonuçları",
      +    fields: "alan{s}",
      +    noFieldsMatch: '"{query}" ile eşleşen alan yok',
      +    configSaved: "Yapılandırma kaydedildi",
      +    yamlConfigSaved: "YAML yapılandırması kaydedildi",
      +    failedToSave: "Kaydedilemedi",
      +    failedToSaveYaml: "YAML kaydedilemedi",
      +    failedToLoadRaw: "Ham yapılandırma yüklenemedi",
      +    configImported: "Yapılandırma içe aktarıldı — gözden geçirip kaydedin",
      +    invalidJson: "Geçersiz JSON dosyası",
      +    categories: {
      +      general: "Genel",
      +      agent: "Agent",
      +      terminal: "Terminal",
      +      display: "Görüntü",
      +      delegation: "Yetkilendirme",
      +      memory: "Bellek",
      +      compression: "Sıkıştırma",
      +      security: "Güvenlik",
      +      browser: "Tarayıcı",
      +      voice: "Ses",
      +      tts: "Metinden Konuşmaya",
      +      stt: "Konuşmadan Metne",
      +      logging: "Günlükleme",
      +      discord: "Discord",
      +      auxiliary: "Yardımcı",
      +    },
      +  },
      +
      +  env: {
      +    changesNote: "Değişiklikler diske hemen kaydedilir. Aktif oturumlar yeni anahtarları otomatik olarak alır.",
      +    confirmClearMessage:
      +      "Bu değişken için saklanan değer .env dosyanızdan kaldırılacak. Bu işlem arayüzden geri alınamaz.",
      +    confirmClearTitle: "Bu anahtar temizlensin mi?",
      +    description: "Şurada saklanan API anahtarlarını ve sırları yönetin",
      +    hideAdvanced: "Gelişmişi Gizle",
      +    showAdvanced: "Gelişmişi Göster",
      +    llmProviders: "LLM Sağlayıcıları",
      +    providersConfigured: "{configured}/{total} sağlayıcı yapılandırıldı",
      +    getKey: "Anahtar al",
      +    notConfigured: "{count} yapılandırılmamış",
      +    notSet: "Ayarlanmadı",
      +    keysCount: "{count} anahtar",
      +    enterValue: "Değer girin...",
      +    replaceCurrentValue: "Mevcut değeri değiştir ({preview})",
      +    showValue: "Gerçek değeri göster",
      +    hideValue: "Değeri gizle",
      +  },
      +
      +  oauth: {
      +    title: "Sağlayıcı Girişleri (OAuth)",
      +    providerLogins: "Sağlayıcı Girişleri (OAuth)",
      +    description: "{connected}/{total} OAuth sağlayıcısı bağlandı. Giriş akışları şu anda CLI üzerinden çalışır; Komutu kopyala'ya tıklayın ve kurmak için bir terminale yapıştırın.",
      +    connected: "Bağlandı",
      +    expired: "Süresi doldu",
      +    notConnected: "Bağlı değil. Bir terminalde {command} komutunu çalıştırın.",
      +    runInTerminal: "bir terminalde.",
      +    noProviders: "OAuth uyumlu sağlayıcı algılanmadı.",
      +    login: "Giriş",
      +    disconnect: "Bağlantıyı kes",
      +    managedExternally: "Harici olarak yönetiliyor",
      +    copied: "Kopyalandı ✓",
      +    cli: "CLI",
      +    copyCliCommand: "CLI komutunu kopyala (harici / yedek için)",
      +    connect: "Bağlan",
      +    sessionExpires: "Oturumun süresi {time} sonra dolacak",
      +    initiatingLogin: "Giriş akışı başlatılıyor…",
      +    exchangingCode: "Kod, jetonlarla değiştiriliyor…",
      +    connectedClosing: "Bağlandı! Kapatılıyor…",
      +    loginFailed: "Giriş başarısız.",
      +    sessionExpired: "Oturum süresi doldu. Yeni bir giriş başlatmak için Yeniden Dene'ye tıklayın.",
      +    reOpenAuth: "Kimlik doğrulama sayfasını yeniden aç",
      +    reOpenVerification: "Doğrulama sayfasını yeniden aç",
      +    submitCode: "Kodu gönder",
      +    pasteCode: "Yetkilendirme kodunu yapıştırın (#state ekiyle de olabilir)",
      +    waitingAuth: "Tarayıcıda yetkilendirmeniz bekleniyor…",
      +    enterCodePrompt: "Yeni bir sekme açıldı. İstenirse bu kodu girin:",
      +    pkceStep1: "claude.ai için yeni bir sekme açıldı. Giriş yapın ve Yetkilendir'e tıklayın.",
      +    pkceStep2: "Yetkilendirmeden sonra gösterilen yetkilendirme kodunu kopyalayın.",
      +    pkceStep3: "Aşağıya yapıştırıp gönderin.",
      +    flowLabels: {
      +      pkce: "Tarayıcı girişi (PKCE)",
      +      device_code: "Cihaz kodu",
      +      external: "Harici CLI",
      +    },
      +    expiresIn: "{time} sonra sona erer",
      +  },
      +
      +  language: {
      +    switchTo: "İngilizce'ye geç",
      +  },
      +
      +  theme: {
      +    title: "Tema",
      +    switchTheme: "Temayı değiştir",
      +  },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "Gerçek oturum geçmişinden kazanılan, koleksiyonluk Hermes rozetleri. Bilinen ama henüz tamamlanmamış başarılar Keşfedildi olarak gösterilir; Gizli başarılar ilk eşleşen davranış görünene kadar saklı kalır.",
      +      scan_subtitle:
      +        "Hermes oturum geçmişi taranıyor. Büyük geçmişlerde ilk tarama 5–10 saniye sürebilir.",
      +    },
      +    actions: {
      +      rescan: "Yeniden tara",
      +    },
      +    stats: {
      +      unlocked: "Açıldı",
      +      unlocked_hint: "kazanılan rozetler",
      +      discovered: "Keşfedildi",
      +      discovered_hint: "biliniyor, henüz kazanılmadı",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "ilk sinyale kadar gizli",
      +      highest_tier: "En yüksek kademe",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "En son",
      +      latest_hint_empty: "Hermes'i daha çok çalıştır",
      +      none_yet: "Henüz yok",
      +    },
      +    state: {
      +      unlocked: "Açıldı",
      +      discovered: "Keşfedildi",
      +      secret: "Gizli",
      +    },
      +    tier: {
      +      target: "Hedef {tier}",
      +      hidden: "Gizli",
      +      complete: "Tamamlandı",
      +      objective: "Amaç",
      +    },
      +    progress: {
      +      hidden: "gizli",
      +    },
      +    scan: {
      +      building_headline: "Başarı profili oluşturuluyor…",
      +      building_detail:
      +        "Oturumlar, araç çağrıları, model meta verileri ve açılma durumu okunuyor.",
      +      starting_headline: "Başarı taraması başlatılıyor…",
      +      progress_detail:
      +        "{total} oturumun {scanned} tanesi tarandı · %{pct}. Daha fazla geçmiş aktıkça rozetler açılır.",
      +      idle_detail:
      +        "Oturumlar, araç çağrıları, model meta verileri ve açılma durumu okunuyor. Rozetler açıldıkça burada görünür.",
      +    },
      +    guide: {
      +      tiers_header: "Kademeler",
      +      secret_header: "Gizli başarılar",
      +      secret_body:
      +        "Sırlar, tetikleyicilerini saklı tutar. Hermes ilgili bir sinyal gördüğünde kart Keşfedildi durumuna geçer ve gereksinimini gösterir.",
      +      scan_status_header: "Tarama durumu",
      +      scan_status_body:
      +        "Hermes yerel geçmişi bir kez tarıyor; sonra kartlar otomatik olarak görünür. Birkaç saniye sürmesi normaldir, hiçbir şey takılmadı.",
      +      what_scanned_header: "Neler taranır",
      +      what_scanned_body:
      +        "Oturumlar, araç çağrıları, model meta verileri, hatalar, başarılar ve yerel açılma durumu.",
      +    },
      +    card: {
      +      share_title: "Bu başarıyı paylaş",
      +      share_label: "{name} paylaş",
      +      share_text: "Paylaş",
      +      how_to_reveal: "Nasıl ortaya çıkarılır",
      +      what_counts: "Neler sayılır",
      +      evidence_label: "Kanıt",
      +      evidence_session_fallback: "oturum",
      +      no_evidence: "Henüz kanıt yok",
      +    },
      +    latest: {
      +      header: "Son açılanlar",
      +    },
      +    empty: {
      +      no_secrets_header: "Bu taramada gizli sır kalmadı.",
      +      no_secrets_body:
      +        "İpucu: sırlar genellikle alışılmadık hata veya ileri kullanıcı kalıplarıyla başlar — port çakışmaları, izin duvarları, eksik ortam değişkenleri, YAML hataları, Docker çakışmaları, geri alma/checkpoint kullanımı, önbellek isabetleri ya da çokça kırmızı yazıdan sonra yapılan ufak düzeltmeler.",
      +    },
      +    filters: {
      +      all_categories: "Tümü",
      +      visibility_all: "tümü",
      +      visibility_unlocked: "açıldı",
      +      visibility_discovered: "keşfedildi",
      +      visibility_secret: "gizli",
      +    },
      +    share: {
      +      dialog_label: "Başarıyı paylaş",
      +      header: "Paylaş: {name}",
      +      close: "Kapat",
      +      rendering: "Oluşturuluyor…",
      +      card_alt: "{name} paylaşım kartı",
      +      error_generic: "Bir şeyler ters gitti.",
      +      x_title: "X'i önceden doldurulmuş bir gönderiyle açar",
      +      x_button: "X'te paylaş",
      +      copy_title: "Görseli kopyalayıp gönderine yapıştır",
      +      copy_button: "Görseli kopyala",
      +      copied: "Kopyalandı ✓",
      +      download_button: "PNG indir",
      +      hint:
      +        "X'te paylaş, yeni sekmede önceden doldurulmuş bir gönderi açar. 1200×630 rozetin eklenmesini istiyorsan önce Görseli kopyala'ya tıkla — X, görseli doğrudan tweet düzenleyiciye yapıştırmana izin verir. PNG indir, dosyayı her yerde kullanmak üzere kaydeder.",
      +      clipboard_unsupported:
      +        "Bu tarayıcıda panoya görsel kopyalama desteklenmiyor — bunun yerine İndir'i kullanın.",
      +      tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
      +    },
      +  },
      +  kanban: {
      +    loading: "Kanban panosu yükleniyor…",
      +    loadFailed: "Kanban panosu yüklenemedi: ",
      +    loadFailedHint:
      +      "Backend, ilk okumada kanban.db'yi otomatik olarak oluşturur. Sorun devam ederse panel günlüklerini kontrol edin.",
      +    board: "Pano",
      +    newBoard: "+ Yeni pano",
      +    newBoardTitle: "Yeni pano",
      +    newBoardDescription:
      +      "Panolar, ilgisiz iş akışlarını ayırmanızı sağlar — proje, depo veya alan başına bir pano. Bir panodaki worker'lar başka bir panonun görevlerini asla görmez.",
      +    slug: "Slug",
      +    slugHint: "— küçük harf, tire, ör. atm10-server",
      +    displayName: "Görünen ad",
      +    displayNameHint: "(isteğe bağlı)",
      +    description: "Açıklama",
      +    descriptionHint: "(isteğe bağlı)",
      +    icon: "Simge",
      +    iconHint: "(tek karakter veya emoji)",
      +    switchAfterCreate: "Oluşturduktan sonra bu panoya geç",
      +    cancel: "İptal",
      +    creating: "Oluşturuluyor…",
      +    createBoard: "Pano oluştur",
      +    search: "Ara",
      +    filterCards: "Kartları filtrele…",
      +    tenant: "Tenant",
      +    allTenants: "Tüm tenant'lar",
      +    assignee: "Atanan kişi",
      +    allProfiles: "Tüm profiller",
      +    showArchived: "Arşivlenenleri göster",
      +    lanesByProfile: "Profile göre şeritler",
      +    nudgeDispatcher: "Dispatcher'ı dürt",
      +    refresh: "Yenile",
      +    selected: "seçili",
      +    complete: "Tamamla",
      +    archive: "Arşivle",
      +    apply: "Uygula",
      +    clear: "Temizle",
      +    createTask: "Bu sütunda görev oluştur",
      +    noTasks: "— görev yok —",
      +    unassigned: "atanmamış",
      +    untitled: "(başlıksız)",
      +    loadingDetail: "Yükleniyor…",
      +    addComment: "Yorum ekle… (göndermek için Enter)",
      +    comment: "Yorum",
      +    status: "Durum",
      +    workspace: "Workspace",
      +    skills: "Beceriler",
      +    createdBy: "Oluşturan",
      +    result: "Result",
      +    comments: "Yorumlar",
      +    events: "Olaylar",
      +    runHistory: "Çalıştırma geçmişi",
      +    workerLog: "Worker günlüğü",
      +    loadingLog: "Günlük yükleniyor…",
      +    noWorkerLog:
      +      "— henüz worker günlüğü yok (görev başlatılmadı veya günlük döndürüldü) —",
      +    noDescription: "— açıklama yok —",
      +    noComments: "— yorum yok —",
      +    edit: "düzenle",
      +    save: "Kaydet",
      +    dependencies: "Bağımlılıklar",
      +    parents: "Üstler:",
      +    children: "Altlar:",
      +    none: "yok",
      +    addParent: "— üst ekle —",
      +    addChild: "— alt ekle —",
      +    removeDependency: "Bağımlılığı kaldır",
      +    block: "Engelle",
      +    unblock: "Engeli kaldır",
      +    notifyHomeChannels: "Ana kanalları bilgilendir",
      +    diagnostics: "Tanılama",
      +    hide: "Gizle",
      +    show: "Göster",
      +    attention: "Dikkat",
      +    tasksNeedAttention: "görev dikkat gerektiriyor",
      +    taskNeedsAttention: "1 görev dikkat gerektiriyor",
      +    diagnostic: "tanılama",
      +    open: "Aç",
      +    close: "Kapat (Esc)",
      +    reassignTo: "Yeniden ata:",
      +    copied: "Kopyalandı",
      +    copyCommand: "Komutu panoya kopyala",
      +    reclaim: "Geri al",
      +    reassign: "Yeniden ata",
      +    renderingError: "Kanban sekmesinde bir oluşturma hatası oluştu",
      +    reloadView: "Görünümü yeniden yükle",
      +    wsAuthFailed:
      +      "WebSocket kimlik doğrulaması başarısız — oturum jetonunu yenilemek için sayfayı yeniden yükleyin.",
      +    markDone: "{n} görev tamamlandı olarak işaretlensin mi?",
      +    markArchived: "{n} görev arşivlensin mi?",
      +    warning: "Uyarı",
      +    phantomIds: "Hayalet ID'ler:",
      +    active: "etkin",
      +    ended: "sona erdi",
      +    noProfile: "(profil yok)",
      +    showAllAttempts: "Tüm denemeleri göster",
      +    sendingUpdates: "Güncellemeler şuraya gönderiliyor",
      +    sendNotifications: "completed / blocked / gave_up bildirimlerini şuraya gönder",
      +    archiveBoardConfirm:
      +      "'{name}' panosu arşivlensin mi? boards/_archived/ dizinine taşınacak, böylece daha sonra kurtarabilirsiniz. Bu panodaki görevler artık UI'nin hiçbir yerinde görünmeyecek.",
      +    archiveBoardTitle: "Bu panoyu arşivle",
      +    boardSwitcherHint: "Panolar, ilgisiz iş akışlarını ayırmanızı sağlar",
      +    taskCreatedWarning: "Görev oluşturuldu, ancak: ",
      +    moveFailed: "Taşıma başarısız: ",
      +    bulkFailed: "Toplu: ",
      +    completionBlockedHallucination: "⚠ Tamamlanma engellendi — hayalet kart ID'leri",
      +    suspectedHallucinatedReferences: "⚠ Metin hayalet kart ID'lerine atıfta bulundu",
      +    pickProfileFirst: "Önce bir profil seçin.",
      +    unblockedMessage: "{id} engeli kaldırıldı. Görev sonraki tick için hazır.",
      +    unblockFailed: "Engel kaldırma başarısız: ",
      +    reclaimedMessage: "{id} geri alındı. Görev tekrar hazır.",
      +    reclaimFailed: "Geri alma başarısız: ",
      +    reassignedMessage: "{id}, {profile} kişisine yeniden atandı.",
      +    reassignFailed: "Yeniden atama başarısız: ",
      +    selectForBulk: "Toplu işlemler için seç",
      +    clickToEdit: "Düzenlemek için tıklayın",
      +    clickToEditAssignee: "Atanan kişiyi düzenlemek için tıklayın",
      +    emptyAssignee: "(boş = atamayı kaldır)",
      +    columnLabels: {
      +      triage: "Triyaj",
      +      todo: "Yapılacak",
      +      ready: "Hazır",
      +      running: "Sürüyor",
      +      blocked: "Engellendi",
      +      done: "Bitti",
      +      archived: "Arşivlendi",
      +    },
      +    columnHelp: {
      +      triage: "Ham fikirler — bir specifier şartnameyi detaylandıracak",
      +      todo: "Bağımlılıklar bekleniyor veya atanmamış",
      +      ready: "Atanmış ve dispatcher tick'i bekleniyor",
      +      running: "Bir worker tarafından alındı — yürütülüyor",
      +      blocked: "Worker insan girdisi istedi",
      +      done: "Tamamlandı",
      +      archived: "Arşivlendi",
      +    },
      +    confirmDone:
      +      "Bu görev tamamlandı olarak işaretlensin mi? Worker'ın sahiplenmesi serbest bırakılır ve bağımlı altlar hazır hale gelir.",
      +    confirmArchive:
      +      "Bu görev arşivlensin mi? Varsayılan pano görünümünden kaybolur.",
      +    confirmBlocked:
      +      "Bu görev engellendi olarak işaretlensin mi? Worker'ın sahiplenmesi serbest bırakılır.",
      +    completionSummary:
      +      "{label} için tamamlanma özeti. Görev result'ı olarak saklanır.",
      +    completionSummaryRequired:
      +      "Bir görevi tamamlandı olarak işaretlemeden önce tamamlanma özeti gereklidir.",
      +    triagePlaceholder: "Kabataslak fikir — yapay zeka şartnameyi yazacak…",
      +    taskTitlePlaceholder: "Yeni görev başlığı…",
      +    specifier: "specifier",
      +    assigneePlaceholder: "atanan",
      +    priority: "Öncelik",
      +    skillsPlaceholder:
      +      "beceriler (isteğe bağlı, virgülle ayrılmış): translation, github-code-review",
      +    noParent: "— üst yok —",
      +    workspacePathDir: "workspace yolu (zorunlu, ör. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "workspace yolu (isteğe bağlı, boşsa atanan kişiden türetilir)",
      +    logTruncated: "(son 100 KB gösteriliyor — tam günlük şurada: ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/types.ts
      @@ -1,4 +1,20 @@
      -export type Locale = "en" | "zh";
      +export type Locale =
      +  | "en"
      +  | "zh"
      +  | "zh-hant"
      +  | "ja"
      +  | "de"
      +  | "es"
      +  | "fr"
      +  | "tr"
      +  | "uk"
      +  | "af"
      +  | "ko"
      +  | "it"
      +  | "ga"
      +  | "pt"
      +  | "ru"
      +  | "hu";
       
       export interface Translations {
         // ── Common ──
      @@ -433,4 +449,251 @@ export interface Translations {
           title: string;
           switchTheme: string;
         };
      +
      +  // ── Achievements plugin (plugins/hermes-achievements) ──
      +  achievements: {
      +    hero: {
      +      kicker: string;
      +      title: string;
      +      subtitle: string;
      +      scan_subtitle: string;
      +    };
      +    actions: {
      +      rescan: string;
      +    };
      +    stats: {
      +      unlocked: string;
      +      unlocked_hint: string;
      +      discovered: string;
      +      discovered_hint: string;
      +      secrets: [redacted:secret-assignment]
      +      secrets_hint: [redacted:secret-assignment]
      +      highest_tier: string;
      +      highest_tier_hint: string;
      +      latest: string;
      +      latest_hint_empty: string;
      +      none_yet: string;
      +    };
      +    state: {
      +      unlocked: string;
      +      discovered: string;
      +      secret: [redacted:secret-assignment]
      +    };
      +    tier: {
      +      target: string;
      +      hidden: string;
      +      complete: string;
      +      objective: string;
      +    };
      +    progress: {
      +      hidden: string;
      +    };
      +    scan: {
      +      building_headline: string;
      +      building_detail: string;
      +      starting_headline: string;
      +      progress_detail: string;
      +      idle_detail: string;
      +    };
      +    guide: {
      +      tiers_header: string;
      +      secret_header: [redacted:secret-assignment]
      +      secret_body: [redacted:secret-assignment]
      +      scan_status_header: string;
      +      scan_status_body: string;
      +      what_scanned_header: string;
      +      what_scanned_body: string;
      +    };
      +    card: {
      +      share_title: string;
      +      share_label: string;
      +      share_text: string;
      +      how_to_reveal: string;
      +      what_counts: string;
      +      evidence_label: string;
      +      evidence_session_fallback: string;
      +      no_evidence: string;
      +    };
      +    latest: {
      +      header: string;
      +    };
      +    empty: {
      +      no_secrets_header: [redacted:secret-assignment]
      +      no_secrets_body: [redacted:secret-assignment]
      +    };
      +    filters: {
      +      all_categories: string;
      +      visibility_all: string;
      +      visibility_unlocked: string;
      +      visibility_discovered: string;
      +      visibility_secret: [redacted:secret-assignment]
      +    };
      +    share: {
      +      dialog_label: string;
      +      header: string;
      +      close: string;
      +      rendering: string;
      +      card_alt: string;
      +      error_generic: string;
      +      x_title: string;
      +      x_button: string;
      +      copy_title: string;
      +      copy_button: string;
      +      copied: string;
      +      download_button: string;
      +      hint: string;
      +      clipboard_unsupported: string;
      +      tweet_text: string;
      +    };
      +  };
      +
      +  // ── Kanban ──
      +  kanban: {
      +    loading: string;
      +    loadFailed: string;
      +    loadFailedHint: string;
      +    board: string;
      +    newBoard: string;
      +    newBoardTitle: string;
      +    newBoardDescription: string;
      +    slug: string;
      +    slugHint: string;
      +    displayName: string;
      +    displayNameHint: string;
      +    description: string;
      +    descriptionHint: string;
      +    icon: string;
      +    iconHint: string;
      +    switchAfterCreate: string;
      +    cancel: string;
      +    creating: string;
      +    createBoard: string;
      +    search: string;
      +    filterCards: string;
      +    tenant: string;
      +    allTenants: string;
      +    assignee: string;
      +    allProfiles: string;
      +    showArchived: string;
      +    lanesByProfile: string;
      +    nudgeDispatcher: string;
      +    refresh: string;
      +    selected: string;
      +    complete: string;
      +    archive: string;
      +    apply: string;
      +    clear: string;
      +    createTask: string;
      +    noTasks: string;
      +    unassigned: string;
      +    untitled: string;
      +    loadingDetail: string;
      +    addComment: string;
      +    comment: string;
      +    status: string;
      +    workspace: string;
      +    skills: string;
      +    createdBy: string;
      +    result: string;
      +    comments: string;
      +    events: string;
      +    runHistory: string;
      +    workerLog: string;
      +    loadingLog: string;
      +    noWorkerLog: string;
      +    noDescription: string;
      +    noComments: string;
      +    edit: string;
      +    save: string;
      +    dependencies: string;
      +    parents: string;
      +    children: string;
      +    none: string;
      +    addParent: string;
      +    addChild: string;
      +    removeDependency: string;
      +    block: string;
      +    unblock: string;
      +    notifyHomeChannels: string;
      +    diagnostics: string;
      +    hide: string;
      +    show: string;
      +    attention: string;
      +    tasksNeedAttention: string;
      +    taskNeedsAttention: string;
      +    diagnostic: string;
      +    open: string;
      +    close: string;
      +    reassignTo: string;
      +    copied: string;
      +    copyCommand: string;
      +    reclaim: string;
      +    reassign: string;
      +    renderingError: string;
      +    reloadView: string;
      +    wsAuthFailed: string;
      +    markDone: string;
      +    markArchived: string;
      +    warning: string;
      +    phantomIds: string;
      +    active: string;
      +    ended: string;
      +    noProfile: string;
      +    showAllAttempts: string;
      +    sendingUpdates: string;
      +    sendNotifications: string;
      +    archiveBoardConfirm: string;
      +    archiveBoardTitle: string;
      +    boardSwitcherHint: string;
      +    taskCreatedWarning: string;
      +    moveFailed: string;
      +    bulkFailed: string;
      +    completionBlockedHallucination: string;
      +    suspectedHallucinatedReferences: string;
      +    pickProfileFirst: string;
      +    unblockedMessage: string;
      +    unblockFailed: string;
      +    reclaimedMessage: string;
      +    reclaimFailed: string;
      +    reassignedMessage: string;
      +    reassignFailed: string;
      +    selectForBulk: string;
      +    clickToEdit: string;
      +    clickToEditAssignee: string;
      +    emptyAssignee: string;
      +    columnLabels: {
      +      triage: string;
      +      todo: string;
      +      ready: string;
      +      running: string;
      +      blocked: string;
      +      done: string;
      +      archived: string;
      +    };
      +    columnHelp: {
      +      triage: string;
      +      todo: string;
      +      ready: string;
      +      running: string;
      +      blocked: string;
      +      done: string;
      +      archived: string;
      +    };
      +    confirmDone: string;
      +    confirmArchive: string;
      +    confirmBlocked: string;
      +    completionSummary: string;
      +    completionSummaryRequired: string;
      +    triagePlaceholder: string;
      +    taskTitlePlaceholder: string;
      +    specifier: string;
      +    assigneePlaceholder: string;
      +    priority: string;
      +    skillsPlaceholder: string;
      +    noParent: string;
      +    workspacePathDir: string;
      +    workspacePathOptional: string;
      +    logTruncated: string;
      +    logAt: string;
      +  };
       }
    • web/src/i18n/uk.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const uk: 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: "[redacted:secret-assignment] токенів",
      +    totalSessions: "Усього сесій",
      +    apiCalls: "Виклики API",
      +    dailyTokenUsage: "[redacted:secret-assignment] використання токенів",
      +    dailyBreakdown: "Щоденна розбивка",
      +    perModelBreakdown: "Розбивка за моделями",
      +    topSkills: "Топ навичок",
      +    skill: "Навичка",
      +    loads: "Агент завантажив",
      +    edits: "Агент керує",
      +    lastUsed: "Останнє використання",
      +    input: "Вхід",
      +    output: "Вихід",
      +    total: "Усього",
      +    noUsageData: "Немає даних про використання за цей період",
      +    startSession: "Почніть сесію, щоб побачити аналітику тут",
      +    date: "Дата",
      +    model: "Модель",
      +    tokens: "[redacted:secret-assignment]",
      +    perDayAvg: "/день у сер.",
      +    acrossModels: "по {count} моделях",
      +    inOut: "{input} вх. / {output} вих.",
      +  },
      +
      +  models: {
      +    modelsUsed: "Використано моделей",
      +    estimatedCost: "Орієнт. вартість",
      +    tokens: "[redacted:secret-assignment]",
      +    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, отримані з реальної історії сеансів. Відомі, але ще не виконані досягнення показані як Виявлені; Секретні досягнення залишаються прихованими, доки не з'явиться перший відповідний сигнал.",
      +      scan_subtitle:
      +        "Сканування історії сеансів Hermes. Перше сканування на великих історіях може тривати 5–10 секунд.",
      +    },
      +    actions: {
      +      rescan: "Повторне сканування",
      +    },
      +    stats: {
      +      unlocked: "Розблоковано",
      +      unlocked_hint: "отримані значки",
      +      discovered: "Виявлено",
      +      discovered_hint: "відомі, ще не отримані",
      +      secrets: "[redacted:secret-assignment]",
      +      secrets_hint: "[redacted:secret-assignment] до першого сигналу",
      +      highest_tier: "Найвищий рівень",
      +      highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
      +      latest: "Останнє",
      +      latest_hint_empty: "запускайте Hermes частіше",
      +      none_yet: "Поки немає",
      +    },
      +    state: {
      +      unlocked: "Розблоковано",
      +      discovered: "Виявлено",
      +      secret: "[redacted:secret-assignment]",
      +    },
      +    tier: {
      +      target: "Ціль {tier}",
      +      hidden: "Приховано",
      +      complete: "Завершено",
      +      objective: "Завдання",
      +    },
      +    progress: {
      +      hidden: "приховано",
      +    },
      +    scan: {
      +      building_headline: "Побудова профілю досягнень…",
      +      building_detail:
      +        "Читання сеансів, викликів інструментів, метаданих моделей і стану розблокування.",
      +      starting_headline: "Запуск сканування досягнень…",
      +      progress_detail:
      +        "Проскановано {scanned} з {total} сеансів · {pct}%. Значки розблоковуються в міру надходження історії.",
      +      idle_detail:
      +        "Читання сеансів, викликів інструментів, метаданих моделей і стану розблокування. Значки з'являються тут у міру розблокування.",
      +    },
      +    guide: {
      +      tiers_header: "Рівні",
      +      secret_header: "[redacted:secret-assignment] досягнення",
      +      secret_body:
      +        "Секрети приховують свій точний тригер. Щойно Hermes побачить пов'язаний сигнал, картка стає Виявленою та показує свою умову.",
      +      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: "[redacted:secret-assignment]",
      +    },
      +    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: "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: "Надсилати сповіщення completed / blocked / gave_up до",
      +    archiveBoardConfirm:
      +      "Архівувати дошку «{name}»? Її буде переміщено до boards/_archived/, тож пізніше її можна відновити. Задачі цієї дошки більше не з'являтимуться в інтерфейсі.",
      +    archiveBoardTitle: "Архівувати цю дошку",
      +    boardSwitcherHint: "Дошки дозволяють розділяти непов'язані потоки роботи",
      +    taskCreatedWarning: "Задачу створено, але: ",
      +    moveFailed: "Переміщення не вдалося: ",
      +    bulkFailed: "Масова дія: ",
      +    completionBlockedHallucination: "⚠ Завершення заблоковано — фантомні id карток",
      +    suspectedHallucinatedReferences: "⚠ Текст посилався на фантомні id карток",
      +    pickProfileFirst: "Спочатку виберіть профіль.",
      +    unblockedMessage: "{id} розблоковано. Задача готова до наступного тіку.",
      +    unblockFailed: "Розблокування не вдалося: ",
      +    reclaimedMessage: "{id} повернуто. Задача знову готова.",
      +    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:
      +      "Позначити цю задачу як виконану? Захоплення воркера буде звільнено, а залежні нащадки стануть готовими.",
      +    confirmArchive:
      +      "Архівувати цю задачу? Вона зникне з типового вигляду дошки.",
      +    confirmBlocked:
      +      "Позначити цю задачу як заблоковану? Захоплення воркера буде звільнено.",
      +    completionSummary:
      +      "Підсумок завершення для {label}. Зберігається як result задачі.",
      +    completionSummaryRequired:
      +      "Підсумок завершення обов'язковий перед позначенням задачі виконаною.",
      +    triagePlaceholder: "Чорнова ідея — ШІ її специфікує…",
      +    taskTitlePlaceholder: "Назва нової задачі…",
      +    specifier: "специфікатор",
      +    assigneePlaceholder: "виконавець",
      +    priority: "Пріоритет",
      +    skillsPlaceholder:
      +      "навички (необов'язково, через кому): translation, github-code-review",
      +    noParent: "— без батька —",
      +    workspacePathDir: "шлях робочої області (обов'язково, напр. ~/projects/my-app)",
      +    workspacePathOptional:
      +      "шлях робочої області (необов'язково, виводиться з виконавця, якщо порожньо)",
      +    logTruncated: "(показано останні 100 KB — повний журнал у ",
      +    logAt: ")",
      +  },
      +};
    • web/src/i18n/zh-hant.ts
      @@ -0,0 +1,696 @@
      +import type { Translations } from "./types";
      +
      +export const zhHant: 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:
      +      "無法載入此外掛的指令碼。請檢查網路請求(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: "排程任務",
      +      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: "Token 總數",
      +    totalSessions: "工作階段總數",
      +    apiCalls: "API 呼叫",
      +    dailyTokenUsage: "每日 Token 用量",
      +    dailyBreakdown: "每日明細",
      +    perModelBreakdown: "各模型用量明細",
      +    topSkills: "常用技能",
      +    skill: "技能",
      +    loads: "代理載入",
      +    edits: "代理管理",
      +    lastUsed: "最近使用",
      +    input: "輸入",
      +    output: "輸出",
      +    total: "總計",
      +    noUsageData: "此時間範圍內無使用資料",
      +    startSession: "開始工作階段後將於此處顯示分析資料",
      +    date: "日期",
      +    model: "模型",
      +    tokens: "Token",
      +    perDayAvg: "/日 平均",
      +    acrossModels: "共 {count} 個模型",
      +    inOut: "輸入 {input} / 輸出 {output}",
      +  },
      +
      +  models: {
      +    modelsUsed: "使用模型數",
      +    estimatedCost: "預估費用",
      +    tokens: "Token",
      +    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}」 — 包括設定、金鑰、記憶、工作階段、技能、排程任務。無法復原。",
      +    created: "已建立",
      +    deleted: "已刪除",
      +    renamed: "已重新命名",
      +  },
      +
      +  pluginsPage: {
      +    contextEngineLabel: "上下文引擎",
      +    dashboardSlots: "面板插槽",
      +    disableRuntime: "停用",
      +    enableAfterInstall: "安裝後啟用",
      +    enableRuntime: "啟用",
      +    forceReinstall: "強制重新安裝(先刪除既有資料夾)",
      +    headline:
      +      "探索、安裝、啟用並更新 Hermes 外掛(對齊 `hermes plugins` CLI)。",
      +    identifierLabel: "Git 網址或 owner/repo",
      +    inactive: "未啟用",
      +    installBtn: "從 Git 安裝",
      +    installHeading: "從 GitHub / Git URL 安裝",
      +    installHint: "可使用 owner/repo 簡寫或完整的 https:// 或 git@ 複製網址。",
      +    memoryProviderLabel: "記憶提供者",
      +    missingEnvWarn: "請先在「金鑰」頁面設定下列項目,外掛才能執行:",
      +    noDashboardTab: "無儀表板分頁",
      +    openTab: "開啟",
      +    orphanHeading: "僅儀表板擴充功能(無對應的 agent plugin.yaml)",
      +    pluginListHeading: "已安裝的外掛",
      +    providerDefaults: "內建 / 預設",
      +    providersHeading: "執行階段提供者外掛",
      +    providersHint:
      +      "會寫入 config.yaml:memory.provider(留空為內建)與 context.engine。下一個工作階段生效。",
      +    refreshDashboard: "重新掃描儀表板擴充功能",
      +    removeConfirm: "從 ~/.hermes/plugins/ 移除此外掛?",
      +    removeHint: "僅可移除位於 ~/.hermes/plugins 下使用者安裝的外掛。",
      +    rescanHeading: "SPA 外掛註冊表",
      +    rescanHint: "在磁碟新增檔案後重新掃描,使儀表板側邊欄載入新的 manifest。",
      +    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 檔案中移除。無法從介面復原。",
      +    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 可收集徽章。已知尚未達成的成就會顯示為「已發現」;秘密成就在首次出現相符行為之前保持隱藏。",
      +      scan_subtitle:
      +        "正在掃描 Hermes 工作階段歷史。在歷史紀錄較多時,首次掃描可能需要 5–10 秒。",
      +    },
      +    actions: {
      +      rescan: "重新掃描",
      +    },
      +    stats: {
      +      unlocked: "已解鎖",
      +      unlocked_hint: "獲得的徽章",
      +      discovered: "已發現",
      +      discovered_hint: "已知,但尚未獲得",
      +      secrets: "秘密",
      +      secrets_hint: "[redacted:secret-assignment]",
      +      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:
      +        "已掃描 {scanned} / {total} 個工作階段 · {pct}%。隨著更多歷史串入,徽章會陸續解鎖。",
      +      idle_detail:
      +        "正在讀取工作階段、工具呼叫、模型中繼資料以及解鎖狀態。徽章解鎖後會顯示在這裡。",
      +    },
      +    guide: {
      +      tiers_header: "等級",
      +      secret_header: "秘密成就",
      +      secret_body:
      +        "秘密成就會隱藏其確切觸發條件。一旦 Hermes 偵測到相關訊號,卡片便會變為「已發現」並顯示其需求。",
      +      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: "[redacted:secret-assignment]",
      +      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: "正在載入看板…",
      +    loadFailed: "載入看板失敗:",
      +    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: "看板分頁發生繪製錯誤",
      +    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}。任務已回到就緒狀態。",
      +    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:
      +      "將此任務標記為完成?工作者的領取將被釋放,下層相依任務將變為就緒。",
      +    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: ")",
      +  },
      +};
    • web/src/i18n/zh.ts
      @@ -421,4 +421,272 @@ export const zh: Translations = {
           title: "主题",
           switchTheme: "切换主题",
         },
      +
      +  achievements: {
      +    hero: {
      +      kicker: "Agentic Gamerscore",
      +      title: "Hermes Achievements",
      +      subtitle:
      +        "从真实会话历史中获得的 Hermes 可收集徽章。已知尚未达成的成就显示为「已发现」;秘密成就在首次出现匹配行为之前保持隐藏。",
      +      scan_subtitle:
      +        "正在扫描 Hermes 会话历史。在历史记录较多时,首次扫描可能需要 5–10 秒。",
      +    },
      +    actions: {
      +      rescan: "重新扫描",
      +    },
      +    stats: {
      +      unlocked: "已解锁",
      +      unlocked_hint: "获得的徽章",
      +      discovered: "已发现",
      +      discovered_hint: "已知,但尚未获得",
      +      secrets: "秘密",
      +      secrets_hint: "[redacted:secret-assignment]",
      +      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:
      +        "已扫描 {scanned} / {total} 个会话 · {pct}%。随着更多历史流入,徽章会陆续解锁。",
      +      idle_detail:
      +        "正在读取会话、工具调用、模型元数据和解锁状态。徽章解锁后将在此显示。",
      +    },
      +    guide: {
      +      tiers_header: "等级",
      +      secret_header: "秘密成就",
      +      secret_body:
      +        "秘密成就会隐藏其确切触发条件。一旦 Hermes 检测到相关信号,卡片将变为「已发现」并显示其要求。",
      +      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: "[redacted:secret-assignment]",
      +      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: "正在加载看板…",
      +    loadFailed: "加载看板失败:",
      +    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: "添加评论…(按回车提交)",
      +    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: "看板标签页发生渲染错误",
      +    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}。任务已回到就绪状态。",
      +    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:
      +      "将此任务标记为完成?工作者将被释放,依赖的子任务将变为就绪。",
      +    confirmArchive:
      +      "归档此任务?它将从默认看板视图中消失。",
      +    confirmBlocked:
      +      "将此任务标记为阻塞?工作者将被释放。",
      +    completionSummary:
      +      "{label} 的完成摘要。这将作为任务结果存储。",
      +    completionSummaryRequired:
      +      "在将任务标记为完成之前,必须提供完成摘要。",
      +    triagePlaceholder: "粗略想法 — AI 将完善规格…",
      +    taskTitlePlaceholder: "新任务标题…",
      +    specifier: "规范制定者",
      +    assigneePlaceholder: "负责人",
      +    priority: "优先级",
      +    skillsPlaceholder:
      +      "技能(可选,逗号分隔):翻译、github-code-review",
      +    noParent: "— 无父任务 —",
      +    workspacePathDir: "工作区路径(必填,例如 ~/projects/my-app)",
      +    workspacePathOptional:
      +      "工作区路径(可选,留空则根据负责人推导)",
      +    logTruncated: "(显示最后 100 KB — 完整日志位于 ",
      +    logAt: ")",
      +  },
       };

3 PRs closed without merging

From the close comments: superseded by another PR 2 · closed without comment 1

  • #21962 feat(i18n): add Afrikaans locale · superseded by another PR → #22914
  • #21970 feat(i18n): add Afrikaans static messages · superseded by another PR → #22914
  • #22465 feat(i18n): add Afrikaans (af) locale · closed without comment
Closed issues · 1
  • Closed#21961Add Afrikaans locale support
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#22914
Closed issues
1
Attempts
3 PRs closed without merging
Fix link checked
10 May 2026

For your agent

The same facts, typed.