Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions agent/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
3. ``display.language`` from config.yaml
4. ``"en"`` (baseline)

Supported languages: en, zh, ja, de, es, fr, tr, uk. Unknown values fall back to en.
Supported languages: en, zh, ja, de, es, fr, pt, pt_BR, tr, uk. Unknown values fall back to en.
"""

from __future__ import annotations
Expand All @@ -39,7 +39,7 @@

logger = logging.getLogger(__name__)

SUPPORTED_LANGUAGES: tuple[str, ...] = ("en", "zh", "ja", "de", "es", "fr", "tr", "uk")
SUPPORTED_LANGUAGES: tuple[str, ...] = ("en", "zh", "ja", "de", "es", "fr", "pt", "pt_BR", "tr", "uk")
DEFAULT_LANGUAGE = "en"

# Accept a few natural aliases so users who type "chinese" / "zh-CN" / "jp"
Expand All @@ -51,6 +51,9 @@
"german": "de", "deutsch": "de", "de-de": "de",
"spanish": "es", "español": "es", "espanol": "es", "es-es": "es", "es-mx": "es",
"french": "fr", "français": "fr", "france": "fr", "fr-fr": "fr", "fr-be": "fr", "fr-ca": "fr", "fr-ch": "fr",
"portuguese": "pt", "português": "pt", "portugues": "pt", "pt-pt": "pt",
"brazilian portuguese": "pt_BR", "português brasileiro": "pt_BR", "portugues brasileiro": "pt_BR",
"pt-br": "pt_BR", "pt_br": "pt_BR",
"ukrainian": "uk", "ukrainisch": "uk", "українська": "uk", "uk-ua": "uk", "ua": "uk",
"turkish": "tr", "türkçe": "tr", "tr-tr": "tr",
}
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ def _ensure_hermes_home_managed(home: Path):
# UI language for static user-facing messages (approval prompts, a
# handful of gateway slash-command replies). Does NOT affect agent
# responses, log lines, tool outputs, or slash-command descriptions.
# Supported: en, zh, ja, de, es, fr, tr, uk. Unknown values fall back to en.
# Supported: en, zh, ja, de, es, fr, pt, pt_BR, tr, uk. Unknown values fall back to en.
"language": "en",
# TUI busy indicator style: kaomoji (default), emoji, unicode (braille
# spinner), or ascii. Live-swappable via `/indicator <style>`.
Expand Down
2 changes: 1 addition & 1 deletion locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# Keys are dotted paths; nesting below is purely for readability. Values may
# contain {placeholder} tokens for str.format substitution. When adding a
# new key, add it to EVERY locale file (en/zh/ja/de/es/fr/tr/uk) in the same commit --
# new key, add it to EVERY locale file (en/zh/ja/de/es/fr/pt/pt_BR/tr/uk) in the same commit --
# tests/agent/test_i18n.py asserts catalog parity.

approval:
Expand Down
24 changes: 24 additions & 0 deletions locales/pt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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 para esta 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: "⚠️ Aprovação expirada (o agente já não está à espera). Peça ao agente para tentar novamente."
draining: "⏳ A aguardar {count} agente(s) ativo(s) antes de reiniciar..."
goal_cleared: "✓ Objetivo limpo."
no_active_goal: "Nenhum 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}"
24 changes: 24 additions & 0 deletions locales/pt_BR.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Catálogo de mensagens estáticas do Hermes -- Português brasileiro
# 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 para esta 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: "⚠️ Aprovação expirada (o agente não está mais esperando). Peça ao agente para tentar novamente."
draining: "⏳ Aguardando {count} agente(s) ativo(s) antes de reiniciar..."
goal_cleared: "✓ Objetivo limpo."
no_active_goal: "Nenhum objetivo ativo."
config_read_failed: "⚠️ Não foi possível ler config.yaml: {error}"
config_save_failed: "⚠️ Não foi possível salvar a configuração: {error}"
4 changes: 4 additions & 0 deletions tests/agent/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_catalog_placeholders_match_english(lang: str):
def test_normalize_lang_accepts_supported():
assert i18n._normalize_lang("zh") == "zh"
assert i18n._normalize_lang("EN") == "en"
assert i18n._normalize_lang("pt") == "pt"


def test_normalize_lang_accepts_aliases():
Expand All @@ -95,6 +96,9 @@ def test_normalize_lang_accepts_aliases():
assert i18n._normalize_lang("Turkish") == "tr"
assert i18n._normalize_lang("tr-TR") == "tr"
assert i18n._normalize_lang("türkçe") == "tr"
assert i18n._normalize_lang("Português") == "pt"
assert i18n._normalize_lang("pt-BR") == "pt_BR"
assert i18n._normalize_lang("português brasileiro") == "pt_BR"


def test_normalize_lang_unknown_falls_back():
Expand Down
4 changes: 2 additions & 2 deletions website/docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1167,14 +1167,14 @@ display:
show_cost: false # Show estimated $ cost in the CLI status bar
tool_preview_length: 0 # Max chars for tool call previews (0 = no limit, show full paths/commands)
runtime_metadata_footer: false # Gateway: append a runtime-context footer to final replies
language: en # UI language for static messages (approval prompts, some gateway replies). en | zh | ja | de | es | fr | tr | uk
language: en # UI language for static messages (approval prompts, some gateway replies). en | zh | ja | de | es | fr | pt | pt_BR | tr | uk
```

### UI language for static messages

The `display.language` setting translates a small set of static user-facing messages — the CLI approval prompt, a handful of gateway slash-command replies (e.g. restart-drain notices, "approval expired", "goal cleared"). It does **not** translate agent responses, log lines, tool output, error tracebacks, or slash-command descriptions — those stay in English. If you want the agent itself to reply in another language, just tell it in your prompt or system message.

Supported values: `en` (default), `zh` (Simplified Chinese), `ja` (Japanese), `de` (German), `es` (Spanish), `fr` (French), `tr` (Turkish), `uk` (Ukrainian). Unknown values fall back to English.
Supported values: `en` (default), `zh` (Simplified Chinese), `ja` (Japanese), `de` (German), `es` (Spanish), `fr` (French), `pt` (Portuguese), `pt_BR` (Brazilian Portuguese), `tr` (Turkish), `uk` (Ukrainian). Unknown values fall back to English.

You can also set this per-session with the `HERMES_LANGUAGE` env var, which overrides the config value.

Expand Down