Skip to content

Commit 0defd49

Browse files
author
TJ Webb
committed
feat(i18n): extract agent manager messages
1 parent 980d66d commit 0defd49

5 files changed

Lines changed: 132 additions & 22 deletions

File tree

code_puppy/agents/agent_manager.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from code_puppy.agents.base_agent import BaseAgent
1717
from code_puppy.agents.json_agent import JSONAgent, discover_json_agents
1818
from code_puppy.callbacks import on_agent_reload, on_register_agents
19+
from code_puppy.i18n import t
1920
from code_puppy.messaging import emit_success, emit_warning
2021
from code_puppy.tools.common import atomic_write_text
2122

@@ -288,7 +289,7 @@ def _discover_agents_locked(message_group_id: Optional[str] = None):
288289
except Exception as e:
289290
# Skip problematic modules
290291
emit_warning(
291-
f"Warning: Could not load agent module {modname}: {e}",
292+
t("agent_manager.discovery.module_load_failed", modname=modname, error=e),
292293
message_group=message_group_id,
293294
)
294295
continue
@@ -330,14 +331,14 @@ def _discover_agents_locked(message_group_id: Optional[str] = None):
330331

331332
except Exception as e:
332333
emit_warning(
333-
f"Warning: Could not load agent {subpkg_name}.{modname}: {e}",
334+
t("agent_manager.discovery.agent_load_failed", agent=f"{subpkg_name}.{modname}", error=e),
334335
message_group=message_group_id,
335336
)
336337
continue
337338

338339
except Exception as e:
339340
emit_warning(
340-
f"Warning: Could not load agent sub-package {subpkg_name}: {e}",
341+
t("agent_manager.discovery.subpackage_load_failed", subpackage=subpkg_name, error=e),
341342
message_group=message_group_id,
342343
)
343344
continue
@@ -356,15 +357,15 @@ def _discover_agents_locked(message_group_id: Optional[str] = None):
356357
if agent_name not in _WARNED_JSON_SHADOWED:
357358
_WARNED_JSON_SHADOWED.add(agent_name)
358359
emit_warning(
359-
f"JSON agent '{agent_name}' skipped: builtin Python agent with the same name takes precedence.",
360+
t("agent_manager.discovery.json_shadowed", agent_name=agent_name),
360361
message_group=message_group_id,
361362
)
362363
continue
363364
_AGENT_REGISTRY[agent_name] = json_path
364365

365366
except Exception as e:
366367
emit_warning(
367-
f"Warning: Could not discover JSON agents: {e}",
368+
t("agent_manager.discovery.json_discovery_failed", error=e),
368369
message_group=message_group_id,
369370
)
370371

@@ -396,7 +397,7 @@ def _discover_agents_locked(message_group_id: Optional[str] = None):
396397

397398
except Exception as e:
398399
emit_warning(
399-
f"Warning: Could not load plugin agents: {e}",
400+
t("agent_manager.discovery.plugin_load_failed", error=e),
400401
message_group=message_group_id,
401402
)
402403

@@ -684,7 +685,7 @@ def clone_agent(agent_name: str) -> Optional[str]:
684685

685686
agent_ref = _AGENT_REGISTRY.get(agent_name)
686687
if agent_ref is None:
687-
emit_warning(f"Agent '{agent_name}' not found for cloning.")
688+
emit_warning(t("agent_manager.clone.source_not_found", agent_name=agent_name))
688689
return None
689690

690691
from ..config import get_agent_pinned_model, get_user_agents_directory
@@ -746,22 +747,22 @@ def clone_agent(agent_name: str) -> Optional[str]:
746747
if pinned_model:
747748
clone_config["model"] = pinned_model
748749
except Exception as exc:
749-
emit_warning(f"Failed to build clone for '{agent_name}': {exc}")
750+
emit_warning(t("agent_manager.clone.build_failed", agent_name=agent_name, error=exc))
750751
return None
751752

752753
if clone_path.exists():
753-
emit_warning(f"Clone target '{clone_name}' already exists.")
754+
emit_warning(t("agent_manager.clone.target_exists", clone_name=clone_name))
754755
return None
755756

756757
try:
757758
atomic_write_text(
758759
str(clone_path),
759760
json.dumps(clone_config, indent=2, ensure_ascii=False),
760761
)
761-
emit_success(f"Cloned '{agent_name}' to '{clone_name}'.")
762+
emit_success(t("agent_manager.clone.success", agent_name=agent_name, clone_name=clone_name))
762763
return clone_name
763764
except Exception as exc:
764-
emit_warning(f"Failed to write clone file '{clone_path}': {exc}")
765+
emit_warning(t("agent_manager.clone.write_failed", clone_path=clone_path, error=exc))
765766
return None
766767

767768

@@ -778,40 +779,40 @@ def delete_clone_agent(agent_name: str) -> bool:
778779
_discover_agents(message_group_id=message_group_id)
779780

780781
if not is_clone_agent_name(agent_name):
781-
emit_warning(f"Agent '{agent_name}' is not a clone.")
782+
emit_warning(t("agent_manager.delete.not_clone", agent_name=agent_name))
782783
return False
783784

784785
if get_current_agent_name() == agent_name:
785-
emit_warning("Cannot delete the active agent. Switch agents first.")
786+
emit_warning(t("agent_manager.delete.active_agent"))
786787
return False
787788

788789
agent_ref = _AGENT_REGISTRY.get(agent_name)
789790
if agent_ref is None:
790-
emit_warning(f"Clone '{agent_name}' not found.")
791+
emit_warning(t("agent_manager.delete.not_found", agent_name=agent_name))
791792
return False
792793

793794
if not isinstance(agent_ref, str):
794-
emit_warning(f"Clone '{agent_name}' is not a JSON agent.")
795+
emit_warning(t("agent_manager.delete.not_json", agent_name=agent_name))
795796
return False
796797

797798
clone_path = Path(agent_ref)
798799
if not clone_path.exists():
799-
emit_warning(f"Clone file for '{agent_name}' does not exist.")
800+
emit_warning(t("agent_manager.delete.file_not_found", agent_name=agent_name))
800801
return False
801802

802803
from ..config import get_user_agents_directory
803804

804805
agents_dir = Path(get_user_agents_directory()).resolve()
805806
if clone_path.resolve().parent != agents_dir:
806-
emit_warning(f"Refusing to delete non-user clone '{agent_name}'.")
807+
emit_warning(t("agent_manager.delete.non_user", agent_name=agent_name))
807808
return False
808809

809810
try:
810811
clone_path.unlink()
811-
emit_success(f"Deleted clone '{agent_name}'.")
812+
emit_success(t("agent_manager.delete.success", agent_name=agent_name))
812813
_AGENT_REGISTRY.pop(agent_name, None)
813814
_AGENT_HISTORIES.pop(agent_name, None)
814815
return True
815816
except Exception as exc:
816-
emit_warning(f"Failed to delete clone '{agent_name}': {exc}")
817+
emit_warning(t("agent_manager.delete.failed", agent_name=agent_name, error=exc))
817818
return False

code_puppy/i18n/locales/en-US.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,5 +349,24 @@
349349
"model_menu.browser.unsupported_provider": "Cannot add model from {provider}: {reason}",
350350
"model_menu.browser.no_tool_call_warning": "⚠️ {model} does NOT support tool calling!\n This model won't be able to edit files, run commands, or use any tools.\n It will be very limited for coding tasks.",
351351
"model_menu.browser.add_cancelled": "Model addition cancelled.",
352-
"cli.autosave.tui_required": "Interactive session browsing needs a terminal. Use -r NAME to resume a session by name."
352+
"cli.autosave.tui_required": "Interactive session browsing needs a terminal. Use -r NAME to resume a session by name.",
353+
"agent_manager.discovery.module_load_failed": "Warning: Could not load agent module {modname}: {error}",
354+
"agent_manager.discovery.agent_load_failed": "Warning: Could not load agent {agent}: {error}",
355+
"agent_manager.discovery.subpackage_load_failed": "Warning: Could not load agent sub-package {subpackage}: {error}",
356+
"agent_manager.discovery.json_shadowed": "JSON agent '{agent_name}' skipped: builtin Python agent with the same name takes precedence.",
357+
"agent_manager.discovery.json_discovery_failed": "Warning: Could not discover JSON agents: {error}",
358+
"agent_manager.discovery.plugin_load_failed": "Warning: Could not load plugin agents: {error}",
359+
"agent_manager.clone.source_not_found": "Agent '{agent_name}' not found for cloning.",
360+
"agent_manager.clone.build_failed": "Failed to build clone for '{agent_name}': {error}",
361+
"agent_manager.clone.target_exists": "Clone target '{clone_name}' already exists.",
362+
"agent_manager.clone.success": "Cloned '{agent_name}' to '{clone_name}'.",
363+
"agent_manager.clone.write_failed": "Failed to write clone file '{clone_path}': {error}",
364+
"agent_manager.delete.not_clone": "Agent '{agent_name}' is not a clone.",
365+
"agent_manager.delete.active_agent": "Cannot delete the active agent. Switch agents first.",
366+
"agent_manager.delete.not_found": "Clone '{agent_name}' not found.",
367+
"agent_manager.delete.not_json": "Clone '{agent_name}' is not a JSON agent.",
368+
"agent_manager.delete.file_not_found": "Clone file for '{agent_name}' does not exist.",
369+
"agent_manager.delete.non_user": "Refusing to delete non-user clone '{agent_name}'.",
370+
"agent_manager.delete.success": "Deleted clone '{agent_name}'.",
371+
"agent_manager.delete.failed": "Failed to delete clone '{agent_name}': {error}"
353372
}

code_puppy/i18n/locales/es.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,24 @@
227227
"model_menu.browser.exited": "✓ Se salió del navegador de modelos",
228228
"model_menu.browser.unsupported_provider": "No se puede agregar un modelo desde {provider}: {reason}",
229229
"model_menu.browser.no_tool_call_warning": "⚠️ {model} NO admite la llamada a herramientas.\n Este modelo no podrá editar archivos, ejecutar comandos ni usar ninguna herramienta.\n Será muy limitado para tareas de programación.",
230-
"model_menu.browser.add_cancelled": "Adición de modelo cancelada."
230+
"model_menu.browser.add_cancelled": "Adición de modelo cancelada.",
231+
"agent_manager.discovery.module_load_failed": "Advertencia: No se pudo cargar el módulo de agente {modname}: {error}",
232+
"agent_manager.discovery.agent_load_failed": "Advertencia: No se pudo cargar el agente {agent}: {error}",
233+
"agent_manager.discovery.subpackage_load_failed": "Advertencia: No se pudo cargar el subpaquete de agentes {subpackage}: {error}",
234+
"agent_manager.discovery.json_shadowed": "Se omitió el agente JSON '{agent_name}': el agente Python incorporado con el mismo nombre tiene prioridad.",
235+
"agent_manager.discovery.json_discovery_failed": "Advertencia: No se pudieron descubrir los agentes JSON: {error}",
236+
"agent_manager.discovery.plugin_load_failed": "Advertencia: No se pudieron cargar los agentes de complementos: {error}",
237+
"agent_manager.clone.source_not_found": "No se encontró el agente '{agent_name}' para clonarlo.",
238+
"agent_manager.clone.build_failed": "No se pudo crear el clon de '{agent_name}': {error}",
239+
"agent_manager.clone.target_exists": "El destino del clon '{clone_name}' ya existe.",
240+
"agent_manager.clone.success": "Se clonó '{agent_name}' como '{clone_name}'.",
241+
"agent_manager.clone.write_failed": "No se pudo escribir el archivo del clon '{clone_path}': {error}",
242+
"agent_manager.delete.not_clone": "El agente '{agent_name}' no es un clon.",
243+
"agent_manager.delete.active_agent": "No se puede eliminar el agente activo. Cambia de agente primero.",
244+
"agent_manager.delete.not_found": "No se encontró el clon '{agent_name}'.",
245+
"agent_manager.delete.not_json": "El clon '{agent_name}' no es un agente JSON.",
246+
"agent_manager.delete.file_not_found": "El archivo del clon '{agent_name}' no existe.",
247+
"agent_manager.delete.non_user": "Se rechazó eliminar el clon de usuario externo '{agent_name}'.",
248+
"agent_manager.delete.success": "Se eliminó el clon '{agent_name}'.",
249+
"agent_manager.delete.failed": "No se pudo eliminar el clon '{agent_name}': {error}"
231250
}

code_puppy/i18n/locales/fr-CA.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,24 @@
227227
"model_menu.browser.exited": "✓ Navigateur de modèles fermé",
228228
"model_menu.browser.unsupported_provider": "Impossible d'ajouter un modèle depuis {provider} : {reason}",
229229
"model_menu.browser.no_tool_call_warning": "⚠️ {model} ne prend PAS en charge l'appel d'outils.\n Ce modèle ne pourra pas modifier des fichiers, exécuter des commandes ou utiliser d'outils.\n Il sera très limité pour les tâches de programmation.",
230-
"model_menu.browser.add_cancelled": "Ajout de modèle annulé."
230+
"model_menu.browser.add_cancelled": "Ajout de modèle annulé.",
231+
"agent_manager.discovery.module_load_failed": "Avertissement : impossible de charger le module d’agent {modname} : {error}",
232+
"agent_manager.discovery.agent_load_failed": "Avertissement : impossible de charger l’agent {agent} : {error}",
233+
"agent_manager.discovery.subpackage_load_failed": "Avertissement : impossible de charger le sous-paquet d’agents {subpackage} : {error}",
234+
"agent_manager.discovery.json_shadowed": "Agent JSON '{agent_name}' ignoré : l’agent Python intégré portant le même nom a priorité.",
235+
"agent_manager.discovery.json_discovery_failed": "Avertissement : impossible de découvrir les agents JSON : {error}",
236+
"agent_manager.discovery.plugin_load_failed": "Avertissement : impossible de charger les agents des modules d’extension : {error}",
237+
"agent_manager.clone.source_not_found": "Agent '{agent_name}' introuvable pour le clonage.",
238+
"agent_manager.clone.build_failed": "Impossible de créer le clone de '{agent_name}' : {error}",
239+
"agent_manager.clone.target_exists": "La cible du clone '{clone_name}' existe déjà.",
240+
"agent_manager.clone.success": "'{agent_name}' cloné vers '{clone_name}'.",
241+
"agent_manager.clone.write_failed": "Impossible d’écrire le fichier du clone '{clone_path}' : {error}",
242+
"agent_manager.delete.not_clone": "L’agent '{agent_name}' n’est pas un clone.",
243+
"agent_manager.delete.active_agent": "Impossible de supprimer l’agent actif. Changez d’agent d’abord.",
244+
"agent_manager.delete.not_found": "Clone '{agent_name}' introuvable.",
245+
"agent_manager.delete.not_json": "Le clone '{agent_name}' n’est pas un agent JSON.",
246+
"agent_manager.delete.file_not_found": "Le fichier du clone '{agent_name}' n’existe pas.",
247+
"agent_manager.delete.non_user": "Refus de supprimer le clone non utilisateur '{agent_name}'.",
248+
"agent_manager.delete.success": "Clone '{agent_name}' supprimé.",
249+
"agent_manager.delete.failed": "Impossible de supprimer le clone '{agent_name}' : {error}"
231250
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Coverage for the agent manager's user-facing message extraction."""
2+
3+
import re
4+
5+
import pytest
6+
7+
from code_puppy.i18n import catalog, pseudo, translate
8+
9+
_PLACEHOLDER = re.compile(r"\{(\w+)\}")
10+
_NAMESPACE = "agent_manager."
11+
12+
13+
@pytest.fixture(autouse=True)
14+
def _reset_locale():
15+
translate.set_locale("en-US")
16+
catalog.reset()
17+
yield
18+
translate.set_locale("en-US")
19+
catalog.reset()
20+
21+
22+
def _keys(locale="en-US"):
23+
return [key for key in catalog.load_catalog(locale) if key.startswith(_NAMESPACE)]
24+
25+
26+
def test_agent_manager_namespace_is_complete_in_all_catalogs():
27+
expected = set(_keys())
28+
assert len(expected) == 19
29+
for locale in ("es", "fr-CA"):
30+
assert set(_keys(locale)) == expected
31+
32+
33+
def test_agent_manager_messages_resolve_and_pseudolocalize():
34+
translate.set_locale("en-US")
35+
assert all(translate.t(key) != key for key in _keys())
36+
translate.set_locale(pseudo.PSEUDO_LOCALE)
37+
assert all(translate.t(key).startswith("⟦") for key in _keys())
38+
39+
40+
def test_catalog_translations_preserve_placeholders():
41+
catalogs = {locale: catalog.load_catalog(locale) for locale in ("en-US", "es", "fr-CA")}
42+
for key in _keys():
43+
source = set(_PLACEHOLDER.findall(catalogs["en-US"][key]))
44+
assert source == set(_PLACEHOLDER.findall(catalogs["es"][key]))
45+
assert source == set(_PLACEHOLDER.findall(catalogs["fr-CA"][key]))
46+
47+
48+
def test_dynamic_values_are_interpolated():
49+
translate.set_locale("es")
50+
assert "agent-x" in translate.t("agent_manager.clone.success", agent_name="agent-x", clone_name="agent-y")
51+
assert "agent-y" in translate.t("agent_manager.clone.success", agent_name="agent-x", clone_name="agent-y")
52+
assert "boom" in translate.t("agent_manager.discovery.plugin_load_failed", error="boom")

0 commit comments

Comments
 (0)