From 11d960fb9b571555943bbecc1d1d3088b6217685 Mon Sep 17 00:00:00 2001 From: jerryfan Date: Sat, 2 May 2026 00:49:28 +0800 Subject: [PATCH] Localize plannotator status summary --- apps/pi-extension/i18n.ts | 48 ++++++++++++++++++++++++++++++++++ apps/pi-extension/index.ts | 11 +++++--- apps/pi-extension/package.json | 1 + 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 apps/pi-extension/i18n.ts diff --git a/apps/pi-extension/i18n.ts b/apps/pi-extension/i18n.ts new file mode 100644 index 000000000..fbe9db4df --- /dev/null +++ b/apps/pi-extension/i18n.ts @@ -0,0 +1,48 @@ +type Locale = "en" | "es" | "fr" | "pt-BR"; +type Params = Record; + +const translations: Record, Record> = { + es: { + "status.phase": "Fase: {phase}", + "status.planFile": "Archivo del plan: {path}", + "status.progress": "Progreso: {done}/{total}", + }, + fr: { + "status.phase": "Phase : {phase}", + "status.planFile": "Fichier du plan : {path}", + "status.progress": "Progression : {done}/{total}", + }, + "pt-BR": { + "status.phase": "Fase: {phase}", + "status.planFile": "Arquivo do plano: {path}", + "status.progress": "Progresso: {done}/{total}", + }, +}; + +let currentLocale: Locale = "en"; + +export function initI18n(pi: { events?: { emit?: (event: string, payload: unknown) => void } }): void { + pi.events?.emit?.("pi-core/i18n/registerBundle", { + namespace: "plannotator", + defaultLocale: "en", + locales: translations, + }); + pi.events?.emit?.("pi-core/i18n/requestApi", { + onReady: (api: { getLocale?: () => string; onLocaleChange?: (cb: (locale: string) => void) => void }) => { + const locale = api.getLocale?.(); + if (isLocale(locale)) currentLocale = locale; + api.onLocaleChange?.((next) => { + if (isLocale(next)) currentLocale = next; + }); + }, + }); +} + +export function t(key: string, fallback: string, params: Params = {}): string { + const template = currentLocale === "en" ? fallback : translations[currentLocale]?.[key] ?? fallback; + return template.replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? `{${name}}`)); +} + +function isLocale(locale: string | undefined): locale is Locale { + return locale === "en" || locale === "es" || locale === "fr" || locale === "pt-BR"; +} diff --git a/apps/pi-extension/index.ts b/apps/pi-extension/index.ts index 782d8ed8e..a3f379cbc 100644 --- a/apps/pi-extension/index.ts +++ b/apps/pi-extension/index.ts @@ -26,6 +26,7 @@ import type { } from "@mariozechner/pi-coding-agent"; import { Key } from "@mariozechner/pi-tui"; import { buildPromptVariables, formatTodoList, loadPlannotatorConfig, renderTemplate, resolvePhaseProfile } from "./config.js"; +import { initI18n, t } from "./i18n.js"; import { type ChecklistItem, markCompletedSteps, @@ -116,6 +117,8 @@ function getPlanReviewAvailabilityWarning(options: { hasUI: boolean; hasPlanHtml } export default function plannotator(pi: ExtensionAPI): void { + initI18n(pi); + let phase: Phase = "idle"; void registerPlannotatorEventListeners(pi); let lastSubmittedPath: string | null = null; @@ -292,13 +295,13 @@ export default function plannotator(pi: ExtensionAPI): void { pi.registerCommand("plannotator-status", { description: "Show plannotator status", handler: async (_args, ctx) => { - const parts = [`Phase: ${phase}`]; + const parts = [t("status.phase", "Phase: {phase}", { phase })]; if (lastSubmittedPath) { - parts.push(`Plan file: ${lastSubmittedPath}`); + parts.push(t("status.planFile", "Plan file: {path}", { path: lastSubmittedPath })); } if (checklistItems.length > 0) { - const done = checklistItems.filter((t) => t.completed).length; - parts.push(`Progress: ${done}/${checklistItems.length}`); + const done = checklistItems.filter((item) => item.completed).length; + parts.push(t("status.progress", "Progress: {done}/{total}", { done, total: checklistItems.length })); } ctx.ui.notify(parts.join("\n"), "info"); }, diff --git a/apps/pi-extension/package.json b/apps/pi-extension/package.json index d5aacceed..e70c5c7fb 100644 --- a/apps/pi-extension/package.json +++ b/apps/pi-extension/package.json @@ -21,6 +21,7 @@ }, "files": [ "index.ts", + "i18n.ts", "plannotator-browser.ts", "plannotator-events.ts", "server.ts",