diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..2b284cd --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,142 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +type Locale = "es" | "fr" | "pt-BR"; +type Key = keyof typeof fallback; +type Params = Record; + +const namespace = "aliou/pi-processes"; + +const fallback = { + "kill.missingId": "Missing required parameter: id", + "kill.notFound": "Process not found: {id}", + "kill.terminated": 'Terminated "{name}" ({id})', + "kill.timeout": + 'SIGTERM timed out for "{name}" ({id}). Run /ps and press x on terminate_timeout to force kill (SIGKILL).', + "kill.failed": 'Failed to terminate "{name}" ({id})', + "list.none": "No background processes running", + "list.summary": "{count} process(es):\n{summary}", + "list.header": "{count} process(es), {running} running/terminating", + "list.field.processes": "Processes", + "list.pid": "pid", + "list.status": "status", + "list.started": "started", + "list.ended": "ended", + "list.runtime": "runtime", + "list.noRunning": "no running process", + "list.finished": "{count} finished", + "list.failed": "{count} failed", + "list.killed": "{count} killed", + "footer.running": "running", + "footer.failed": "failed", + "footer.killed": "killed", +} as const; + +const translations: Record>> = { + es: { + "kill.missingId": "Falta el parámetro requerido: id", + "kill.notFound": "Proceso no encontrado: {id}", + "kill.terminated": 'Proceso terminado "{name}" ({id})', + "kill.timeout": + 'SIGTERM agotó el tiempo para "{name}" ({id}). Ejecuta /ps y pulsa x en terminate_timeout para forzar la finalización (SIGKILL).', + "kill.failed": 'No se pudo terminar "{name}" ({id})', + "list.none": "No hay procesos en segundo plano en ejecución", + "list.summary": "{count} proceso(s):\n{summary}", + "list.header": "{count} proceso(s), {running} en ejecución/terminando", + "list.field.processes": "Procesos", + "list.pid": "pid", + "list.status": "estado", + "list.started": "iniciado", + "list.ended": "finalizado", + "list.runtime": "duración", + "list.noRunning": "ningún proceso en ejecución", + "list.finished": "{count} finalizados", + "list.failed": "{count} fallidos", + "list.killed": "{count} terminados", + "footer.running": "en ejecución", + "footer.failed": "fallidos", + "footer.killed": "terminados", + }, + fr: { + "kill.missingId": "Paramètre requis manquant : id", + "kill.notFound": "Processus introuvable : {id}", + "kill.terminated": 'Processus terminé "{name}" ({id})', + "kill.timeout": + 'SIGTERM a expiré pour "{name}" ({id}). Exécutez /ps et appuyez sur x sur terminate_timeout pour forcer l’arrêt (SIGKILL).', + "kill.failed": 'Impossible de terminer "{name}" ({id})', + "list.none": "Aucun processus en arrière-plan en cours", + "list.summary": "{count} processus :\n{summary}", + "list.header": "{count} processus, {running} en cours/en arrêt", + "list.field.processes": "Processus", + "list.pid": "pid", + "list.status": "état", + "list.started": "démarré", + "list.ended": "terminé", + "list.runtime": "durée", + "list.noRunning": "aucun processus en cours", + "list.finished": "{count} terminés", + "list.failed": "{count} échoués", + "list.killed": "{count} tués", + "footer.running": "en cours", + "footer.failed": "échoués", + "footer.killed": "tués", + }, + "pt-BR": { + "kill.missingId": "Parâmetro obrigatório ausente: id", + "kill.notFound": "Processo não encontrado: {id}", + "kill.terminated": 'Processo encerrado "{name}" ({id})', + "kill.timeout": + 'SIGTERM esgotou o tempo para "{name}" ({id}). Execute /ps e pressione x em terminate_timeout para forçar o encerramento (SIGKILL).', + "kill.failed": 'Falha ao encerrar "{name}" ({id})', + "list.none": "Nenhum processo em segundo plano em execução", + "list.summary": "{count} processo(s):\n{summary}", + "list.header": "{count} processo(s), {running} em execução/encerrando", + "list.field.processes": "Processos", + "list.pid": "pid", + "list.status": "status", + "list.started": "iniciado", + "list.ended": "finalizado", + "list.runtime": "duração", + "list.noRunning": "nenhum processo em execução", + "list.finished": "{count} finalizados", + "list.failed": "{count} com falha", + "list.killed": "{count} encerrados", + "footer.running": "em execução", + "footer.failed": "com falha", + "footer.killed": "encerrados", + }, +}; + +let currentLocale: string | undefined; + +function format(template: string, params: Params = {}): string { + return template.replace(/\{(\w+)\}/g, (_match, key) => + String(params[key] ?? `{${key}}`), + ); +} + +export function t(key: Key, params?: Params): string { + const locale = currentLocale as Locale | undefined; + const template = locale ? translations[locale]?.[key] : undefined; + return format(template ?? fallback[key], params); +} + +export function initI18n(pi: ExtensionAPI): void { + pi.events?.emit?.("pi-core/i18n/registerBundle", { + namespace, + defaultLocale: "en", + fallback, + translations, + }); + pi.events?.on?.("pi-core/i18n/localeChanged", (event: unknown) => { + currentLocale = + event && typeof event === "object" && "locale" in event + ? String((event as { locale?: unknown }).locale ?? "") + : undefined; + }); + pi.events?.emit?.("pi-core/i18n/requestApi", { + namespace, + onApi(api: { getLocale?: () => string | undefined }) { + currentLocale = api.getLocale?.(); + }, + }); +} diff --git a/src/index.ts b/src/index.ts index 98a98d7..b46aba6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,10 +3,13 @@ import { setupProcessesCommands } from "./commands"; import { registerProcessesSettings } from "./commands/settings"; import { configLoader } from "./config"; import { setupProcessesHooks } from "./hooks"; +import { initI18n } from "./i18n"; import { ProcessManager } from "./manager"; import { setupProcessesTools } from "./tools"; export default async function (pi: ExtensionAPI) { + initI18n(pi); + if (process.platform === "win32") { pi.on("session_start", async (_event, ctx) => { if (!ctx.hasUI) return; diff --git a/src/tools/actions/kill.ts b/src/tools/actions/kill.ts index e8b01a2..6941a58 100644 --- a/src/tools/actions/kill.ts +++ b/src/tools/actions/kill.ts @@ -1,6 +1,7 @@ import { ToolCallHeader } from "@aliou/pi-utils-ui"; import type { Theme } from "@mariozechner/pi-coding-agent"; import type { ExecuteResult } from "../../constants"; +import { t } from "../../i18n"; import type { ProcessManager } from "../../manager"; interface KillParams { @@ -24,18 +25,18 @@ export async function executeKill( ): Promise { if (!params.id) { return { - content: [{ type: "text", text: "Missing required parameter: id" }], + content: [{ type: "text", text: t("kill.missingId") }], details: { action: "kill", success: false, - message: "Missing required parameter: id", + message: t("kill.missingId"), }, }; } const proc = manager.get(params.id); if (!proc) { - const message = `Process not found: ${params.id}`; + const message = t("kill.notFound", { id: params.id }); return { content: [{ type: "text", text: message }], details: { @@ -52,7 +53,7 @@ export async function executeKill( }); if (result.ok) { - const message = `Terminated "${proc.name}" (${proc.id})`; + const message = t("kill.terminated", { name: proc.name, id: proc.id }); return { content: [{ type: "text", text: message }], details: { @@ -64,9 +65,7 @@ export async function executeKill( } if (result.reason === "timeout") { - const message = - `SIGTERM timed out for "${proc.name}" (${proc.id}). ` + - "Run /ps and press x on terminate_timeout to force kill (SIGKILL)."; + const message = t("kill.timeout", { name: proc.name, id: proc.id }); return { content: [{ type: "text", text: message }], details: { @@ -77,7 +76,7 @@ export async function executeKill( }; } - const message = `Failed to terminate "${proc.name}" (${proc.id})`; + const message = t("kill.failed", { name: proc.name, id: proc.id }); return { content: [{ type: "text", text: message }], details: { diff --git a/src/tools/actions/list.ts b/src/tools/actions/list.ts index 1e9b915..6e73194 100644 --- a/src/tools/actions/list.ts +++ b/src/tools/actions/list.ts @@ -6,6 +6,7 @@ import type { } from "@mariozechner/pi-coding-agent"; import { Text } from "@mariozechner/pi-tui"; import type { ExecuteResult, ProcessesDetails } from "../../constants"; +import { t } from "../../i18n"; import type { ProcessManager } from "../../manager"; import { formatRuntime, @@ -20,11 +21,11 @@ export function executeList(manager: ProcessManager): ExecuteResult { if (processes.length === 0) { return { - content: [{ type: "text", text: "No background processes running" }], + content: [{ type: "text", text: t("list.none") }], details: { action: "list", success: true, - message: "No background processes running", + message: t("list.none"), processes: [], }, }; @@ -37,7 +38,7 @@ export function executeList(manager: ProcessManager): ExecuteResult { ) .join("\n"); - const message = `${processes.length} process(es):\n${summary}`; + const message = t("list.summary", { count: processes.length, summary }); return { content: [{ type: "text", text: message }], details: { @@ -61,8 +62,8 @@ export function renderListResult( { fields: [ { - label: "Processes", - value: "No background processes running", + label: t("list.field.processes"), + value: t("list.none"), showCollapsed: true, }, ], @@ -104,7 +105,7 @@ export function renderListResult( const lines: string[] = [ theme.fg( "success", - `${processes.length} process(es), ${runningCount} running/terminating`, + t("list.header", { count: processes.length, running: runningCount }), ), ]; @@ -113,10 +114,10 @@ export function renderListResult( lines.push( [ `- ${theme.fg("accent", process.name)} ${theme.fg("muted", `(${process.id})`)}`, - ` pid: ${process.pid} status: ${status}`, - ` started: ${theme.fg("muted", formatTimestamp(process.startTime))}`, - ` ended: ${theme.fg("muted", formatTimestamp(process.endTime))}`, - ` runtime: ${theme.fg("muted", formatRuntime(process.startTime, process.endTime))}`, + ` ${t("list.pid")}: ${process.pid} ${t("list.status")}: ${status}`, + ` ${t("list.started")}: ${theme.fg("muted", formatTimestamp(process.startTime))}`, + ` ${t("list.ended")}: ${theme.fg("muted", formatTimestamp(process.endTime))}`, + ` ${t("list.runtime")}: ${theme.fg("muted", formatRuntime(process.startTime, process.endTime))}`, ].join("\n"), ); } @@ -145,17 +146,17 @@ export function renderListResult( `${theme.fg("accent", `"${p.name}"`)} [${formatStatusTag(p, theme)}]`, ) .join(", ") - : theme.fg("muted", "no running process"); + : theme.fg("muted", t("list.noRunning")); const restParts: string[] = []; - if (finishedOk > 0) restParts.push(`${finishedOk} finished`); - if (failed > 0) restParts.push(`${failed} failed`); - if (killed > 0) restParts.push(`${killed} killed`); + if (finishedOk > 0) restParts.push(t("list.finished", { count: finishedOk })); + if (failed > 0) restParts.push(t("list.failed", { count: failed })); + if (killed > 0) restParts.push(t("list.killed", { count: killed })); const restSummary = restParts.length > 0 ? theme.fg("muted", ` + ${restParts.join(", ")}`) : ""; fields.push({ - label: "Processes", + label: t("list.field.processes"), value: runningSummary + restSummary, showCollapsed: true, }); @@ -165,13 +166,16 @@ export function renderListResult( value: string; }> = []; if (runningCount > 0) { - footerItems.push({ label: "running", value: String(runningCount) }); + footerItems.push({ + label: t("footer.running"), + value: String(runningCount), + }); } if (failed > 0) { - footerItems.push({ label: "failed", value: String(failed) }); + footerItems.push({ label: t("footer.failed"), value: String(failed) }); } if (killed > 0) { - footerItems.push({ label: "killed", value: String(killed) }); + footerItems.push({ label: t("footer.killed"), value: String(killed) }); } return new ToolBody(