i18n: traduzioni it/fr per tutti gli agenti

This commit is contained in:
2026-07-19 09:30:25 +01:00
parent c389962e3c
commit ffea3f41f9
15 changed files with 201 additions and 5 deletions
+10
View File
@@ -2,6 +2,16 @@
"name": "Business Analyst", "name": "Business Analyst",
"description": "Stress-tests a business idea/plan against provided evidence; finds flaws, proposes fixes, gives a GO/NO-GO/PIVOT verdict. Does no web research — reasons from inputs.", "description": "Stress-tests a business idea/plan against provided evidence; finds flaws, proposes fixes, gives a GO/NO-GO/PIVOT verdict. Does no web research — reasons from inputs.",
"friendly_description": "Acts as a ruthless startup advisor: takes your business idea plus the market data you have, finds every flaw, and tells you whether it is worth pursuing.", "friendly_description": "Acts as a ruthless startup advisor: takes your business idea plus the market data you have, finds every flaw, and tells you whether it is worth pursuing.",
"i18n": {
"it": {
"name": "Analista Aziendale",
"friendly_description": "Agisce come un consulente startup impietoso: prende la tua idea imprenditoriale e i dati di mercato che hai, trova ogni difetto e ti dice se vale la pena perseguirla."
},
"fr": {
"name": "Analyste d'Affaires",
"friendly_description": "Agit comme un conseiller startup impitoyable : prend votre idée d'entreprise et les données de marché que vous avez, trouve chaque faille et vous dit si elle vaut la peine d'être poursuivie."
}
},
"instructions": "Pass the idea, the draft business plan, and any market/competitor evidence you have. Specify an output path/dir for the critique report. The more evidence you provide, the sharper the critique — missing evidence is flagged as open questions, not guessed.", "instructions": "Pass the idea, the draft business plan, and any market/competitor evidence you have. Specify an output path/dir for the critique report. The more evidence you provide, the sharper the critique — missing evidence is flagged as open questions, not guessed.",
"type": "task", "type": "task",
"scope": "reasoning", "scope": "reasoning",
+10
View File
@@ -2,6 +2,16 @@
"name": "Code Explorer", "name": "Code Explorer",
"description": "Studies code, investigates bugs, analyses architecture, and produces structured Markdown reports in data/explorer/. No implementation, no planning — just analysis and reporting.", "description": "Studies code, investigates bugs, analyses architecture, and produces structured Markdown reports in data/explorer/. No implementation, no planning — just analysis and reporting.",
"friendly_description": "Investigates a codebase or a bug and writes up what it finds as a structured report — analysis only, never touches the code.", "friendly_description": "Investigates a codebase or a bug and writes up what it finds as a structured report — analysis only, never touches the code.",
"i18n": {
"it": {
"name": "Esploratore di Codice",
"friendly_description": "Analizza un codebase o un bug e scrive un report strutturato — solo analisi, non tocca mai il codice."
},
"fr": {
"name": "Explorateur de Code",
"friendly_description": "Examine un codebase ou un bug et rédige un rapport structuré de ses découvertes — analyse uniquement, ne touche jamais au code."
}
},
"instructions": "Give it a concrete question or area to investigate (a bug, a module, an architecture concern). It writes a Markdown report to data/explorer/ and returns a summary. It never edits code or plans work.", "instructions": "Give it a concrete question or area to investigate (a bug, a module, an architecture concern). It writes a Markdown report to data/explorer/ and returns a summary. It never edits code or plans work.",
"type": "task", "type": "task",
"scope": "reasoning", "scope": "reasoning",
+10
View File
@@ -2,6 +2,16 @@
"name": "Generalist", "name": "Generalist",
"description": "General-purpose task executor: carries out the well-defined work ordered by the calling agent — file edits, shell commands, batch operations. No planning or QA.", "description": "General-purpose task executor: carries out the well-defined work ordered by the calling agent — file edits, shell commands, batch operations. No planning or QA.",
"friendly_description": "Carries out well-defined hands-on work — file edits, shell commands, batch operations — exactly as instructed.", "friendly_description": "Carries out well-defined hands-on work — file edits, shell commands, batch operations — exactly as instructed.",
"i18n": {
"it": {
"name": "Generalista",
"friendly_description": "Esegue lavori pratici ben definiti — modifiche a file, comandi shell, operazioni batch — esattamente come richiesto."
},
"fr": {
"name": "Généraliste",
"friendly_description": "Exécute un travail pratique bien défini — modifications de fichiers, commandes shell, opérations par lots — exactement comme demandé."
}
},
"instructions": "Hand it a fully-specified task: what to change and where. It executes but does not plan, decide scope, or QA its own output, so be explicit about the desired outcome.", "instructions": "Hand it a fully-specified task: what to change and where. It executes but does not plan, decide scope, or QA its own output, so be explicit about the desired outcome.",
"type": "task", "type": "task",
"scope": "general", "scope": "general",
+10
View File
@@ -2,6 +2,16 @@
"name": "Companion", "name": "Companion",
"description": "A warm, patient companion for younger members. Listens, encourages, helps with homework without doing it for them, sparks creativity, and treats sensitive topics with care. Adapts tone and vocabulary to the child's age, which is provided in the injected user profile.", "description": "A warm, patient companion for younger members. Listens, encourages, helps with homework without doing it for them, sparks creativity, and treats sensitive topics with care. Adapts tone and vocabulary to the child's age, which is provided in the injected user profile.",
"friendly_description": "A warm companion for children — listens, helps with homework, sparks creativity, and adapts to each child.", "friendly_description": "A warm companion for children — listens, helps with homework, sparks creativity, and adapts to each child.",
"i18n": {
"it": {
"name": "Compagno",
"friendly_description": "Un compagno affettuoso per bambini — ascolta, aiuta con i compiti, stimola la creatività e si adatta a ogni bambino."
},
"fr": {
"name": "Compagnon",
"friendly_description": "Un compagnon chaleureux pour les enfants — écoute, aide aux devoirs, stimule la créativité et s'adapte à chaque enfant."
}
},
"type": "chat", "type": "chat",
"inject_memory": ["user-memory/index.md", "shared-memory/index.md"], "inject_memory": ["user-memory/index.md", "shared-memory/index.md"],
"strength": "average", "strength": "average",
+10
View File
@@ -2,6 +2,16 @@
"name": "Main Assistant", "name": "Main Assistant",
"description": "General-purpose assistant: helps the user with any task using tools, and persists all relevant information in memory", "description": "General-purpose assistant: helps the user with any task using tools, and persists all relevant information in memory",
"friendly_description": "Your general-purpose assistant — helps with any task and remembers what matters in memory.", "friendly_description": "Your general-purpose assistant — helps with any task and remembers what matters in memory.",
"i18n": {
"it": {
"name": "Assistente Principale",
"friendly_description": "Il tuo assistente tuttofare — ti aiuta in qualsiasi attività e ricorda ciò che conta nella memoria."
},
"fr": {
"name": "Assistant Principal",
"friendly_description": "Votre assistant polyvalent — vous aide dans toutes vos tâches et retient l'essentiel en mémoire."
}
},
"type": "chat", "type": "chat",
"inject_memory": ["user-memory/index.md", "shared-memory/index.md"], "inject_memory": ["user-memory/index.md", "shared-memory/index.md"],
"icon": "icon.png", "icon": "icon.png",
+10
View File
@@ -2,6 +2,16 @@
"name": "Project Coordinator", "name": "Project Coordinator",
"description": "Conversational coordinator for a single project of any kind — software, but also travel, study, writing, events, personal goals, research. Holds the project context, does everyday planning/writing itself, and delegates specialized work (research, or code via tech-lead/software-architect/software-engineer) to sub-agents via execute_task. The user talks to one bot that already knows the project.", "description": "Conversational coordinator for a single project of any kind — software, but also travel, study, writing, events, personal goals, research. Holds the project context, does everyday planning/writing itself, and delegates specialized work (research, or code via tech-lead/software-architect/software-engineer) to sub-agents via execute_task. The user talks to one bot that already knows the project.",
"friendly_description": "A conversational coordinator that holds the full context of one project — software or otherwise — does the everyday planning and writing, and delegates specialised work to sub-agents.", "friendly_description": "A conversational coordinator that holds the full context of one project — software or otherwise — does the everyday planning and writing, and delegates specialised work to sub-agents.",
"i18n": {
"it": {
"name": "Coordinatore di Progetto",
"friendly_description": "Un coordinatore conversazionale che mantiene il contesto completo di un progetto — software o altro — gestisce la pianificazione e la scrittura quotidiana e delega il lavoro specializzato a sub-agenti."
},
"fr": {
"name": "Coordinateur de Projet",
"friendly_description": "Un coordinateur conversationnel qui garde le contexte complet d'un projet — logiciel ou autre — gère la planification et la rédaction quotidiennes et délègue le travail spécialisé à des sous-agents."
}
},
"type": "chat", "type": "chat",
"scope": "reasoning", "scope": "reasoning",
"strength": "average", "strength": "average",
+10
View File
@@ -2,6 +2,16 @@
"name": "Researcher", "name": "Researcher",
"description": "Multi-step web research; returns a structured summary and saves findings to the scratchpad.", "description": "Multi-step web research; returns a structured summary and saves findings to the scratchpad.",
"friendly_description": "Does multi-step web research and gives you back a structured summary, saving its sources to the scratchpad.", "friendly_description": "Does multi-step web research and gives you back a structured summary, saving its sources to the scratchpad.",
"i18n": {
"it": {
"name": "Ricercatore",
"friendly_description": "Conduce ricerche web articolate e ti restituisce un riepilogo strutturato, salvando le fonti negli appunti."
},
"fr": {
"name": "Chercheur",
"friendly_description": "Effectue des recherches web multi-étapes et vous restitue un résumé structuré, en sauvegardant ses sources dans le bloc-notes."
}
},
"instructions": "Pass a specific research question; optionally hint at depth (how many sources) or a time horizon. Optionally specify an output file/dir in the prompt to write the report outside the default `data/research/`. Returns a path + one-line summary, also saved to the scratchpad.", "instructions": "Pass a specific research question; optionally hint at depth (how many sources) or a time horizon. Optionally specify an output file/dir in the prompt to write the report outside the default `data/research/`. Returns a path + one-line summary, also saved to the scratchpad.",
"type": "task", "type": "task",
"scope": "general", "scope": "general",
+10
View File
@@ -2,6 +2,16 @@
"name": "Software Architect", "name": "Software Architect",
"description": "Plans code changes and delegates to software-engineer.", "description": "Plans code changes and delegates to software-engineer.",
"friendly_description": "Plans a code change end-to-end and drives the software-engineer agent to implement it.", "friendly_description": "Plans a code change end-to-end and drives the software-engineer agent to implement it.",
"i18n": {
"it": {
"name": "Architetto del Software",
"friendly_description": "Pianifica una modifica al codice dall'inizio alla fine e guida l'agente ingegnere del software per implementarla."
},
"fr": {
"name": "Architecte Logiciel",
"friendly_description": "Planifie une modification de code de bout en bout et dirige l'agent ingénieur logiciel pour l'implémenter."
}
},
"instructions": "Describe the change or feature and the relevant part of the codebase. It produces an implementation plan and may delegate the actual edits to software-engineer. Use it when the work needs design before coding.", "instructions": "Describe the change or feature and the relevant part of the codebase. It produces an implementation plan and may delegate the actual edits to software-engineer. Use it when the work needs design before coding.",
"type": "task", "type": "task",
"scope": "reasoning", "scope": "reasoning",
+10
View File
@@ -2,6 +2,16 @@
"name": "Software Engineer", "name": "Software Engineer",
"description": "Writes and modifies source files across any file type.", "description": "Writes and modifies source files across any file type.",
"friendly_description": "Writes and edits source files to implement a change you describe.", "friendly_description": "Writes and edits source files to implement a change you describe.",
"i18n": {
"it": {
"name": "Ingegnere del Software",
"friendly_description": "Scrive e modifica file sorgente per implementare la modifica che descrivi."
},
"fr": {
"name": "Ingénieur Logiciel",
"friendly_description": "Écrit et modifie des fichiers source pour implémenter le changement que vous décrivez."
}
},
"instructions": "Give it a clear, scoped implementation task: which files or behaviour to change and the intended result. Best for executing an already-decided design — pair with software-architect when the approach is still open.", "instructions": "Give it a clear, scoped implementation task: which files or behaviour to change and the intended result. Best for executing an already-decided design — pair with software-architect when the approach is still open.",
"type": "task", "type": "task",
"scope": "coding", "scope": "coding",
+10
View File
@@ -2,6 +2,16 @@
"name": "Spec Writer", "name": "Spec Writer",
"description": "Transforms project ideas and specifications into comprehensive, unambiguous Markdown documentation. Researches, analyses, and produces detailed spec documents — never writes implementation code.", "description": "Transforms project ideas and specifications into comprehensive, unambiguous Markdown documentation. Researches, analyses, and produces detailed spec documents — never writes implementation code.",
"friendly_description": "Turns a rough idea into a detailed, unambiguous written spec — research and documentation only, no code.", "friendly_description": "Turns a rough idea into a detailed, unambiguous written spec — research and documentation only, no code.",
"i18n": {
"it": {
"name": "Scrittore di Specifiche",
"friendly_description": "Trasforma un'idea grezza in una specifica dettagliata e inequivocabile — solo ricerca e documentazione, nessun codice."
},
"fr": {
"name": "Rédacteur de Spécifications",
"friendly_description": "Transforme une idée brute en une spécification écrite détaillée et sans ambiguïté — recherche et documentation uniquement, pas de code."
}
},
"instructions": "Provide the idea, the goals, and any constraints. It researches and produces a thorough Markdown spec document. It never writes implementation code — use it before building, not during.", "instructions": "Provide the idea, the goals, and any constraints. It researches and produces a thorough Markdown spec document. It never writes implementation code — use it before building, not during.",
"type": "task", "type": "task",
"scope": "reasoning", "scope": "reasoning",
+10
View File
@@ -2,6 +2,16 @@
"name": "Tech Lead", "name": "Tech Lead",
"description": "Reads project documentation or high-level requirements, breaks them into implementation tasks, sequences them by dependency, and orchestrates software-architect/software-engineer sub-agents to build the project end-to-end.", "description": "Reads project documentation or high-level requirements, breaks them into implementation tasks, sequences them by dependency, and orchestrates software-architect/software-engineer sub-agents to build the project end-to-end.",
"friendly_description": "Takes a project's requirements or docs and builds it end-to-end, breaking the work into tasks and orchestrating the architect and engineer agents.", "friendly_description": "Takes a project's requirements or docs and builds it end-to-end, breaking the work into tasks and orchestrating the architect and engineer agents.",
"i18n": {
"it": {
"name": "Responsabile Tecnico",
"friendly_description": "Prende i requisiti o la documentazione di un progetto e lo realizza dall'inizio alla fine, suddividendo il lavoro in attività e orchestrando gli agenti architetto e ingegnere."
},
"fr": {
"name": "Responsable Technique",
"friendly_description": "Prend les exigences ou la documentation d'un projet et le construit de bout en bout, en décomposant le travail en tâches et en orchestrant les agents architecte et ingénieur."
}
},
"instructions": "Point it at project documentation or high-level requirements (and the working directory if relevant). It decomposes the work, sequences tasks by dependency, and orchestrates software-architect/software-engineer to deliver. Best for whole-project builds, not single edits.", "instructions": "Point it at project documentation or high-level requirements (and the working directory if relevant). It decomposes the work, sequences tasks by dependency, and orchestrates software-architect/software-engineer to deliver. Best for whole-project builds, not single edits.",
"type": "task", "type": "task",
"scope": "reasoning", "scope": "reasoning",
+10
View File
@@ -2,6 +2,16 @@
"name": "TIC", "name": "TIC",
"description": "Hidden background agent. Spawned periodically by the scheduler. Processes pending MCP events (email, WhatsApp, calendar), evaluates relevance, and notifies the user via notify() when something is worth surfacing. Ephemeral: session is discarded as soon as the turn ends.", "description": "Hidden background agent. Spawned periodically by the scheduler. Processes pending MCP events (email, WhatsApp, calendar), evaluates relevance, and notifies the user via notify() when something is worth surfacing. Ephemeral: session is discarded as soon as the turn ends.",
"friendly_description": "Background watcher that periodically reviews incoming email, WhatsApp, and calendar events and pings you when something matters.", "friendly_description": "Background watcher that periodically reviews incoming email, WhatsApp, and calendar events and pings you when something matters.",
"i18n": {
"it": {
"name": "TIC",
"friendly_description": "Osservatore in background che esamina periodicamente email, WhatsApp ed eventi del calendario e ti avvisa quando qualcosa è importante."
},
"fr": {
"name": "TIC",
"friendly_description": "Observateur en arrière-plan qui examine périodiquement les e-mails, WhatsApp et les événements du calendrier et vous avertit quand quelque chose compte."
}
},
"type": "system", "type": "system",
"inject_skills": false, "inject_skills": false,
"inject_memory": ["data/memory/index.md", "data/notifications.md"], "inject_memory": ["data/memory/index.md", "data/notifications.md"],
+42
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
@@ -22,12 +24,28 @@ pub enum AgentType {
System, System,
} }
/// Per-locale overrides for the **user-facing** fields of an agent, keyed by
/// locale in `meta.json` under `i18n`. Only `name` and `friendly_description`
/// are translatable — the routing `description` (read by the orchestrator LLM)
/// and the prompt stay canonical. Any absent field falls back to the top-level
/// (English) value.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LocalizedMeta {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub friendly_description: Option<String>,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct RawMeta { struct RawMeta {
name: String, name: String,
description: String, description: String,
#[serde(default)] #[serde(default)]
friendly_description: Option<String>, friendly_description: Option<String>,
/// Translations of the user-facing fields, keyed by locale (`"it"`, `"fr"`…).
#[serde(default)]
i18n: HashMap<String, LocalizedMeta>,
#[serde(default)] #[serde(default)]
instructions: Option<String>, instructions: Option<String>,
#[serde(default)] #[serde(default)]
@@ -62,6 +80,11 @@ pub struct AgentMeta {
/// the frontend falls back to `description`. Applies to every agent type. /// the frontend falls back to `description`. Applies to every agent type.
#[serde(default)] #[serde(default)]
pub friendly_description: Option<String>, pub friendly_description: Option<String>,
/// Translations of the user-facing fields (`name` / `friendly_description`), keyed
/// by locale. Resolved server-side by [`AgentMeta::localize`] before the meta is sent
/// to the frontend — never serialized out, so all translations stay on the box.
#[serde(default, skip_serializing)]
pub i18n: HashMap<String, LocalizedMeta>,
/// Note for the **calling LLM** on *how* to invoke this agent for the best result /// Note for the **calling LLM** on *how* to invoke this agent for the best result
/// (expected inputs, format, gotchas). Kept short. Only meaningful for `task` agents — /// (expected inputs, format, gotchas). Kept short. Only meaningful for `task` agents —
/// it is surfaced solely via `list_items` (type=agents), which already lists task /// it is surfaced solely via `list_items` (type=agents), which already lists task
@@ -99,6 +122,23 @@ pub struct AgentMeta {
pub icon: Option<String>, pub icon: Option<String>,
} }
impl AgentMeta {
/// Overwrite the user-facing `name` / `friendly_description` with the
/// translation for `locale` when `meta.json` provides one, leaving each
/// field untouched (English fallback) when the locale or the specific field
/// is absent. The routing `description` and prompt are never localized.
pub fn localize(&mut self, locale: &str) {
if let Some(loc) = self.i18n.get(locale).cloned() {
if let Some(name) = loc.name {
self.name = name;
}
if loc.friendly_description.is_some() {
self.friendly_description = loc.friendly_description;
}
}
}
}
/// Scan `agents/` and return metadata for every agent that has both /// Scan `agents/` and return metadata for every agent that has both
/// `meta.json` and `AGENT.md`. Skips the `common/` directory. /// `meta.json` and `AGENT.md`. Skips the `common/` directory.
pub fn discover() -> Result<Vec<AgentMeta>> { pub fn discover() -> Result<Vec<AgentMeta>> {
@@ -146,6 +186,7 @@ pub fn discover() -> Result<Vec<AgentMeta>> {
name: raw.name, name: raw.name,
description: raw.description, description: raw.description,
friendly_description: raw.friendly_description, friendly_description: raw.friendly_description,
i18n: raw.i18n,
instructions: raw.instructions, instructions: raw.instructions,
inject_memory: raw.inject_memory, inject_memory: raw.inject_memory,
client: raw.client, client: raw.client,
@@ -178,6 +219,7 @@ pub fn load_meta(agent_id: &str) -> Result<AgentMeta> {
name: raw.name, name: raw.name,
description: raw.description, description: raw.description,
friendly_description: raw.friendly_description, friendly_description: raw.friendly_description,
i18n: raw.i18n,
instructions: raw.instructions, instructions: raw.instructions,
inject_memory: raw.inject_memory, inject_memory: raw.inject_memory,
client: raw.client, client: raw.client,
+29 -4
View File
@@ -1,4 +1,4 @@
use axum::{Json, extract::State, response::IntoResponse}; use axum::{Extension, Json, extract::State, response::IntoResponse};
use serde::Serialize; use serde::Serialize;
use skald_core::agents::AgentMeta; use skald_core::agents::AgentMeta;
@@ -7,9 +7,31 @@ use std::sync::Arc;
use skald_core::skald::Skald; use skald_core::skald::Skald;
use super::ApiError; use super::ApiError;
use super::guard::AuthUser;
pub async fn list(_: State<Arc<Skald>>) -> Result<Json<Vec<AgentMeta>>, ApiError> { /// The caller's effective UI locale: their `users.locale` override, else the
let agents = skald_core::agents::discover()?; /// instance default, else English. Used to localize the user-facing agent
/// fields before they leave the process.
async fn caller_locale(skald: &Skald, user_id: &str) -> String {
let user_locale = skald
.users()
.get(user_id)
.await
.ok()
.flatten()
.and_then(|u| u.locale);
skald_core::i18n::resolve_locale(skald.db(), user_locale.as_deref()).await
}
pub async fn list(
State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
) -> Result<Json<Vec<AgentMeta>>, ApiError> {
let locale = caller_locale(&skald, &auth.user_id).await;
let mut agents = skald_core::agents::discover()?;
for agent in &mut agents {
agent.localize(&locale);
}
Ok(Json(agents)) Ok(Json(agents))
} }
@@ -22,9 +44,12 @@ pub struct AgentDetail {
pub async fn get( pub async fn get(
State(skald): State<Arc<Skald>>, State(skald): State<Arc<Skald>>,
Extension(auth): Extension<AuthUser>,
axum::extract::Path(id): axum::extract::Path<String>, axum::extract::Path(id): axum::extract::Path<String>,
) -> Result<Json<AgentDetail>, ApiError> { ) -> Result<Json<AgentDetail>, ApiError> {
let meta = skald_core::agents::load_meta(&id)?; let locale = caller_locale(&skald, &auth.user_id).await;
let mut meta = skald_core::agents::load_meta(&id)?;
meta.localize(&locale);
let prompt = skald_core::agents::load_prompt(&id)?; let prompt = skald_core::agents::load_prompt(&id)?;
let all = skald.llm_manager().list_models_info().await; let all = skald.llm_manager().list_models_info().await;
let models = sort_models_for_agent(all, meta.scope.as_deref(), meta.strength); let models = sort_models_for_agent(all, meta.scope.as_deref(), meta.strength);
+10 -1
View File
@@ -39,7 +39,16 @@ export class AgentsPage extends LightElement {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.__onLocaleChanged = () => this.requestUpdate(); this.__onLocaleChanged = () => {
// The user-facing agent name/description are localized server-side, so a
// language switch must refetch — a bare re-render would keep the strings
// fetched under the previous locale.
if (this._open) {
if (this._detail) this._openDetail(this._detail.meta);
else this._loadList();
}
this.requestUpdate();
};
window.addEventListener('locale-changed', this.__onLocaleChanged); window.addEventListener('locale-changed', this.__onLocaleChanged);
window.addEventListener('llm-page-change', (e) => { window.addEventListener('llm-page-change', (e) => {
this._open = e.detail.page === 'agents'; this._open = e.detail.page === 'agents';