hermes - 💡(How to fix) Fix PyPI wheel omits `locales/` catalogs, causing gateway slash commands to show raw i18n keys

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

print(f'{k} => {t(k, model="TEST_MODEL", provider="TEST_PROVIDER", tokens="123", cost="$0", capabilities="tools", error="ERR", count=1)}')

Root Cause

Suspected Root Cause

Fix Action

Workaround

No durable local workaround was applied. Directly patching the pipx-installed runtime or manually creating site-packages/locales/ would be overwritten by updates and is not ideal for production use.

Code Example

gateway.model.switched
gateway.model.provider_label
gateway.model.context_label
gateway.model.max_output_label
gateway.model.cost_label
gateway.model.capabilities_label
gateway.model.session_only_hint
gateway.reasoning.status

---

python - <<'PY'
from agent.i18n import _locales_dir, get_language, t
keys = [
    'gateway.model.switched',
    'gateway.model.provider_label',
    'gateway.model.context_label',
    'gateway.model.max_output_label',
    'gateway.model.cost_label',
    'gateway.model.capabilities_label',
    'gateway.model.session_only_hint',
    'gateway.model.current_label',
    'gateway.model.usage_switch_model',
    'gateway.model.error_prefix',
    'gateway.reasoning.status',
    'gateway.approval_expired',
    'gateway.draining',
]
print('language=', get_language())
print('locales_dir=', _locales_dir())
print('locales_exists=', _locales_dir().exists())
for k in keys:
    print(f'{k} => {t(k, model="TEST_MODEL", provider="TEST_PROVIDER", tokens="123", cost="$0", capabilities="tools", error="ERR", count=1)}')
PY

---

Switched model to <model>
Provider: <provider>
Context: <tokens>
...

---

language= en
locales_dir= /home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages/locales
locales_exists= False
gateway.model.switched => gateway.model.switched
gateway.model.provider_label => gateway.model.provider_label
gateway.model.context_label => gateway.model.context_label
gateway.model.max_output_label => gateway.model.max_output_label
gateway.model.cost_label => gateway.model.cost_label
gateway.model.capabilities_label => gateway.model.capabilities_label
gateway.model.session_only_hint => gateway.model.session_only_hint
gateway.model.current_label => gateway.model.current_label
gateway.model.usage_switch_model => gateway.model.usage_switch_model
gateway.model.error_prefix => gateway.model.error_prefix
gateway.approval_expired => gateway.approval_expired
gateway.draining => gateway.draining

---

site-packages/hermes_agent-0.15.2.dist-info/RECORD
locales entries: 0
site-packages/locales exists: False

---

Hermes Agent v0.15.2 (2026.5.29.2)
Install type: pipx / PyPI wheel
Project: /home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages
Python: 3.12.3
OpenAI SDK: 2.24.0
Update status: Up to date
Platform: Linux, OCI Ubuntu, aarch64
Gateway platform: Telegram

---

Path(__file__).resolve().parent.parent / "locales"

---

/home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages/locales

---

if value is None:
    value = key

---

t("gateway.model.switched", model=result.new_model)
t("gateway.model.provider_label", provider=plabel)
t("gateway.model.context_label", tokens=f"{ctx:,}")
t("gateway.model.max_output_label", tokens=f"{mi.max_output:,}")
t("gateway.model.cost_label", cost=mi.format_cost())
t("gateway.model.capabilities_label", capabilities=mi.format_capabilities())
t("gateway.model.session_only_hint")

---

diff --git a/locales/__init__.py b/locales/__init__.py
new file mode 100644
index 0000000..003044f
--- /dev/null
+++ b/locales/__init__.py
@@ -0,0 +1 @@
+"""Bundled Hermes translation catalogs."""
diff --git a/pyproject.toml b/pyproject.toml
index 6f56536..7940932 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -224,6 +224,7 @@ py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajector
 [tool.setuptools.package-data]
 hermes_cli = ["web_dist/**/*", "tui_dist/**/*", "scripts/install.sh", "scripts/install.ps1"]
 gateway = ["assets/**/*"]
+locales = ["*.yaml"]
 plugins = [
   "*/dashboard/manifest.json",
   "*/dashboard/dist/*",
@@ -239,7 +240,7 @@ plugins = [
 ]
 
 [tool.setuptools.packages.find]
-include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"]
+include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*", "locales"]

---

locales entries: 17
locales/__init__.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
RAW_BUFFERClick to expand / collapse

Bug Description

The PyPI/pipx-installed hermes-agent wheel appears to omit the top-level locales/*.yaml translation catalogs used by agent.i18n. As a result, gateway slash-command replies that call t(...) can display raw translation keys instead of user-facing text.

Observed examples include:

gateway.model.switched
gateway.model.provider_label
gateway.model.context_label
gateway.model.max_output_label
gateway.model.cost_label
gateway.model.capabilities_label
gateway.model.session_only_hint
gateway.reasoning.status

This was observed from Telegram Gateway after using commands such as /model and /reasoning. The underlying command may still succeed, but the confirmation/status text is rendered as raw dotted i18n keys.

Steps to Reproduce

  1. Install/run Hermes Agent from the PyPI wheel, e.g. via pipx.
  2. Enable and run Hermes Gateway with Telegram.
  3. In a Telegram chat/topic connected to Hermes, run /model and switch/select a model, or run /reasoning.
  4. Observe the gateway response.

Additional local runtime probe:

python - <<'PY'
from agent.i18n import _locales_dir, get_language, t
keys = [
    'gateway.model.switched',
    'gateway.model.provider_label',
    'gateway.model.context_label',
    'gateway.model.max_output_label',
    'gateway.model.cost_label',
    'gateway.model.capabilities_label',
    'gateway.model.session_only_hint',
    'gateway.model.current_label',
    'gateway.model.usage_switch_model',
    'gateway.model.error_prefix',
    'gateway.reasoning.status',
    'gateway.approval_expired',
    'gateway.draining',
]
print('language=', get_language())
print('locales_dir=', _locales_dir())
print('locales_exists=', _locales_dir().exists())
for k in keys:
    print(f'{k} => {t(k, model="TEST_MODEL", provider="TEST_PROVIDER", tokens="123", cost="$0", capabilities="tools", error="ERR", count=1)}')
PY

Expected Behavior

Gateway responses should show translated/user-facing strings, e.g. something like:

Switched model to <model>
Provider: <provider>
Context: <tokens>
...

If a non-English catalog is incomplete, missing keys should fall back to English, not to raw dotted key names.

Actual Behavior

The gateway sends raw i18n keys directly to the user.

Direct probe output from the affected install:

language= en
locales_dir= /home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages/locales
locales_exists= False
gateway.model.switched => gateway.model.switched
gateway.model.provider_label => gateway.model.provider_label
gateway.model.context_label => gateway.model.context_label
gateway.model.max_output_label => gateway.model.max_output_label
gateway.model.cost_label => gateway.model.cost_label
gateway.model.capabilities_label => gateway.model.capabilities_label
gateway.model.session_only_hint => gateway.model.session_only_hint
gateway.model.current_label => gateway.model.current_label
gateway.model.usage_switch_model => gateway.model.usage_switch_model
gateway.model.error_prefix => gateway.model.error_prefix
gateway.approval_expired => gateway.approval_expired
gateway.draining => gateway.draining

Wheel/package inspection also showed:

site-packages/hermes_agent-0.15.2.dist-info/RECORD
locales entries: 0
site-packages/locales exists: False

Environment

Hermes Agent v0.15.2 (2026.5.29.2)
Install type: pipx / PyPI wheel
Project: /home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages
Python: 3.12.3
OpenAI SDK: 2.24.0
Update status: Up to date
Platform: Linux, OCI Ubuntu, aarch64
Gateway platform: Telegram

Suspected Root Cause

agent/i18n.py resolves locale catalogs from:

Path(__file__).resolve().parent.parent / "locales"

In this pipx-installed package, that resolves to:

/home/ubuntu/.local/share/pipx/venvs/hermes-agent/lib/python3.12/site-packages/locales

But the locales/ directory is missing entirely.

The repo contains locales/*.yaml, but pyproject.toml appears to only declare package data for hermes_cli, gateway, and plugins; the top-level locales directory is not packaged into the wheel. Since t() cannot find the active language catalog and cannot find the English fallback catalog either, it falls back to returning the key itself:

if value is None:
    value = key

Relevant Code Path

gateway/run.py builds /model confirmation output with calls such as:

t("gateway.model.switched", model=result.new_model)
t("gateway.model.provider_label", provider=plabel)
t("gateway.model.context_label", tokens=f"{ctx:,}")
t("gateway.model.max_output_label", tokens=f"{mi.max_output:,}")
t("gateway.model.cost_label", cost=mi.format_cost())
t("gateway.model.capabilities_label", capabilities=mi.format_capabilities())
t("gateway.model.session_only_hint")

Impact

  • The underlying slash command may still succeed.
  • User-facing gateway responses look broken and confusing.
  • This is likely not limited to /model or /reasoning; any static gateway text using agent.i18n.t() can expose raw dotted keys if the packaged catalog is absent.

Proposed Fix

Make locales an installable package and include its YAML catalogs as package data:

  • add locales/__init__.py
  • add locales = ["*.yaml"] under [tool.setuptools.package-data]
  • include locales in [tool.setuptools.packages.find].include

Patch sketch:

diff --git a/locales/__init__.py b/locales/__init__.py
new file mode 100644
index 0000000..003044f
--- /dev/null
+++ b/locales/__init__.py
@@ -0,0 +1 @@
+"""Bundled Hermes translation catalogs."""
diff --git a/pyproject.toml b/pyproject.toml
index 6f56536..7940932 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -224,6 +224,7 @@ py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajector
 [tool.setuptools.package-data]
 hermes_cli = ["web_dist/**/*", "tui_dist/**/*", "scripts/install.sh", "scripts/install.ps1"]
 gateway = ["assets/**/*"]
+locales = ["*.yaml"]
 plugins = [
   "*/dashboard/manifest.json",
   "*/dashboard/dist/*",
@@ -239,7 +240,7 @@ plugins = [
 ]
 
 [tool.setuptools.packages.find]
-include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"]
+include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "hermes_cli.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*", "locales"]

Local wheel verification after the patch reportedly showed:

locales entries: 17
locales/__init__.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

Additional Hardening Ideas

  1. Add a regression test that imports agent.i18n, checks _locales_dir() exists in an installed package layout, and verifies common gateway keys do not return themselves.
  2. Consider logging a warning when the English fallback catalog is missing, since returning raw keys is user-visible.

Workaround

No durable local workaround was applied. Directly patching the pipx-installed runtime or manually creating site-packages/locales/ would be overwritten by updates and is not ideal for production use.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix PyPI wheel omits `locales/` catalogs, causing gateway slash commands to show raw i18n keys